Modelado temporal y espacial con redes neuronales¶

  • Predicción de series temporales univariadas y multivariadas con redes LSTM
  • Detección de objetos con redes convolucionales

Long Short-Term Memory en analítica empresarial¶

El uso de modelos LSTM en el modelado de series temporales en el contexto de la analítica empresarial ofrece numerosas ventajas y aplicaciones.

Las LSTM pueden ser útiles en los siguientes ámbitos:

  1. Predicción de ventas y demanda: Las LSTM pueden ser entrenadas para predecir ventas futuras y demanda de productos en función de datos históricos de ventas, precios, promociones, etc. Esto ayuda a las empresas a planificar mejor la producción, gestión de inventario y estrategias de marketing.

  2. Análisis de datos financieros: Las LSTM pueden utilizarse para predecir tendencias financieras, como el precio de las acciones, los tipos de interés, los flujos de efectivo y los ingresos futuros. Esto proporciona información valiosa para la toma de decisiones financieras y la gestión del riesgo.

  3. Optimización de la cadena de suministro: Al predecir la demanda de productos y materias primas, las LSTM pueden ayudar a optimizar la cadena de suministro al garantizar un suministro adecuado de productos y minimizar los costos de almacenamiento y transporte.

  4. Gestión de recursos humanos: Las LSTM pueden utilizarse para prever la demanda de personal en función de patrones históricos de actividad empresarial. Esto facilita la planificación de la fuerza laboral, la gestión de turnos y la asignación de recursos humanos de manera más eficiente.

  5. Detección de anomalías: Al modelar series temporales, las LSTM pueden identificar patrones anómalos o comportamientos inusuales en los datos empresariales, como fraudes financieros, fallos en el equipo, o cambios repentinos en el comportamiento del cliente.

  6. Optimización de la producción y mantenimiento predictivo: Las LSTM pueden predecir el rendimiento futuro de los equipos de producción y prever cuándo es probable que se produzcan fallos o averías. Esto permite a las empresas planificar el mantenimiento de manera proactiva y evitar costosas interrupciones en la producción.

En resumen, las LSTM son herramientas poderosas en el modelado de series temporales en analítica empresarial, ya que permiten predecir patrones y tendencias futuras en los datos, lo que ayuda a las empresas a tomar decisiones más informadas, optimizar operaciones y mejorar la eficiencia en diversas áreas funcionales.

Red Neuronal Recurrente¶

Una Red Neuronal Recurrente (RNN, por sus siglas en inglés, Recurrent Neural Network) es un tipo de arquitectura de red neuronal diseñada para trabajar con datos secuenciales o de serie temporal, donde la secuencia de entrada tiene una relación temporal o de dependencia entre los puntos de datos. A diferencia de las redes neuronales feedforward tradicionales, donde la información fluye en una sola dirección, de la entrada a la salida, en una RNN la información puede circular a través de ciclos, lo que le permite a la red mantener y utilizar estados internos (memoria) para procesar secuencias de datos de longitud variable.

Las características principales de una RNN incluyen:

  1. Conexiones recurrentes: Las unidades neuronales en una RNN están conectadas consigo mismas, lo que permite que la información se propague a lo largo del tiempo a través de ciclos en la red. Esta estructura recurrente le permite a la RNN modelar dependencias temporales en los datos.

  2. Memoria a corto plazo: Las RNN tienen la capacidad de mantener información sobre los estados anteriores a través del tiempo. Esto permite que la red recuerde la información relevante de los pasos de tiempo anteriores y la utilice para tomar decisiones en pasos de tiempo posteriores.

  3. Aplicaciones en secuencias temporales: Las RNN son especialmente útiles para modelar datos secuenciales o de serie temporal, como texto, audio, series de tiempo financieras o biológicas, y mucho más. Pueden utilizarse para tareas como traducción automática, reconocimiento de voz, predicción de series temporales y generación de texto, entre otras.

  4. Arquitecturas variadas: A lo largo del tiempo, se han desarrollado varias arquitecturas de RNN para abordar diferentes desafíos y mejorar el rendimiento en diversas tareas. Algunas variantes populares incluyen las redes neuronales LSTM (Long Short-Term Memory) y las redes neuronales GRU (Gated Recurrent Unit), que están diseñadas para abordar el problema del desvanecimiento del gradiente y permitir un mejor aprendizaje de dependencias a largo plazo.

Una Red Neuronal Recurrente es una arquitectura de red neuronal que está diseñada específicamente para trabajar con datos secuenciales, aprovechando su capacidad para mantener y utilizar la información de estados anteriores para procesar secuencias de datos de longitud variable y modelar dependencias temporales en los datos.

drawing

Modelos RNN¶

Existen varias arquitecturas de Redes Neuronales Recurrentes (RNN) diseñadas para abordar diferentes desafíos y mejorar el rendimiento en diversas tareas de procesamiento de secuencias temporales. A continuación, describiré algunos de los modelos de RNN más comunes:

  1. RNN Estándar (Simple RNN):

    • Es la forma más básica de RNN, donde cada unidad neuronal tiene conexiones recurrentes que le permiten mantener un estado interno o memoria.
    • Las unidades simples RNN pueden sufrir de problemas de desvanecimiento del gradiente, lo que limita su capacidad para recordar dependencias a largo plazo en los datos.
  2. Long Short-Term Memory (LSTM):

    • Las LSTM fueron diseñadas para abordar el problema del desvanecimiento del gradiente en las RNN estándar.
    • Introducen una estructura más compleja de unidades neuronales que incluyen puertas de control de flujo de información (puertas de olvido, puertas de entrada y puertas de salida) para regular el flujo de información a través de la red.
    • Las LSTM pueden recordar dependencias a largo plazo en los datos y son especialmente útiles en tareas donde se requiere una memoria a largo plazo, como la traducción automática o la generación de texto.
  3. Gated Recurrent Unit (GRU):

    • Las GRU son una variante simplificada de las LSTM que también abordan el problema del desvanecimiento del gradiente.
    • Tienen menos parámetros que las LSTM y son más fáciles de entrenar en conjuntos de datos más pequeños.
    • A pesar de su simplicidad, las GRU han demostrado ser efectivas en una amplia gama de tareas de procesamiento de secuencias.
  4. Bidirectional RNN (BRNN):

    • Las BRNN utilizan dos conjuntos de unidades recurrentes, una que procesa la secuencia de entrada en orden directo y otra que procesa la secuencia en orden inverso.
    • Esta arquitectura permite capturar dependencias contextuales tanto hacia adelante como hacia atrás en la secuencia, lo que puede mejorar el rendimiento en tareas de modelado de lenguaje y comprensión del habla.
  5. Redes Neuronales Recurrentes Apiladas (Stacked RNN):

    • En lugar de tener una sola capa de unidades recurrentes, las RNN apiladas tienen múltiples capas de unidades recurrentes una encima de la otra.
    • Esta arquitectura permite capturar representaciones jerárquicas y complejas de las secuencias de datos, lo que puede mejorar el rendimiento en tareas de procesamiento de lenguaje natural y reconocimiento de voz.

Estos son algunos de los modelos de RNN más comunes utilizados en la actualidad. Cada uno tiene sus propias ventajas y desventajas, y la elección del modelo dependerá de la tarea específica y las características de los datos.

Modelo LSTM (Long Short-Term Memory)¶

Un modelo LSTM (Long Short-Term Memory) es un tipo de red neuronal recurrente (RNN) diseñado para modelar secuencias de datos y capturar dependencias a largo plazo en los datos de entrada. A diferencia de las RNN tradicionales, que pueden tener dificultades para recordar información relevante de largo plazo debido al problema del desvanecimiento del gradiente, los modelos LSTM están diseñados para mitigar este problema mediante el uso de unidades de memoria llamadas "celdas de memoria" y puertas de control de flujo de información.

Las características clave de un modelo LSTM incluyen:

  1. Celdas de Memoria: Cada unidad LSTM contiene una celda de memoria que puede mantener y actualizar información durante un período de tiempo prolongado. Esta celda de memoria permite al modelo retener información relevante a largo plazo.

  2. Puertas de Olvido, Entrada y Salida: Las LSTM utilizan tres tipos de puertas para controlar el flujo de información dentro de la red:

    • Puerta de olvido (Forget Gate): Decide qué información de la celda de memoria anterior debe ser olvidada.
    • Puerta de entrada (Input Gate): Decide qué nueva información debe ser almacenada en la celda de memoria.
    • Puerta de salida (Output Gate): Decide qué información de la celda de memoria se utilizará para calcular la salida de la unidad LSTM.
  3. Funciones de Activación: Las LSTM utilizan funciones de activación (como la función sigmoide y la función tangente hiperbólica) para controlar el flujo de información a través de las puertas y calcular la salida de la celda de memoria.

  4. Aplicaciones: Los modelos LSTM se utilizan en una variedad de aplicaciones de aprendizaje automático, como procesamiento del lenguaje natural (NLP), series temporales, reconocimiento de voz y más. Se pueden utilizar para predecir, clasificar o generar datos secuenciales.

Un modelo LSTM es una arquitectura de red neuronal recurrente especializada en modelar secuencias de datos y capturar dependencias a largo plazo. Su capacidad para retener información relevante a lo largo del tiempo lo hace especialmente efectivo en problemas donde las dependencias temporales son importantes.

Celda (neurona) LSTM¶

La celda de memoria en una unidad LSTM (Long Short-Term Memory) está diseñada para mantener y actualizar información a lo largo del tiempo en una secuencia de datos. La matemática detrás de una celda LSTM implica varias operaciones que controlan el flujo de información dentro de la unidad.

A continuación una descripción de las operaciones matemáticas típicas en una celda LSTM:

  1. Entradas ponderadas y sumas ponderadas:

    • Dadas las entradas $x_t$ en el instante de tiempo $t$ y las salidas de la unidad anterior $h_{t-1}$, se calculan las entradas ponderadas para las puertas de olvido, entrada y salida, y la celda de memoria actual $c_t$:

    $$i_t = \sigma(W_{xi}x_t + W_{hi}h_{t-1} + b_i)$$ $$f_t = \sigma(W_{xf}x_t + W_{hf}h_{t-1} + b_f)$$ $$o_t = \sigma(W_{xo}x_t + W_{ho}h_{t-1} + b_o)$$ $$g_t = \tanh(W_{xg}x_t + W_{hg}h_{t-1} + b_g)$$

    Donde:

    • $W$ son las matrices de pesos para las entradas $x_t$ y $h_{t-1}$ en cada puerta, y $b$ son los sesgos correspondientes.
    • $\sigma$ es la función de activación sigmoide y $\tanh$ es la función tangente hiperbólica.
  2. Actualización de la celda de memoria:

    • Se actualiza la celda de memoria $c_t$ combinando la información anterior $c_{t-1}$ con la nueva información candidata $g_t$ mediante la puerta de olvido y la puerta de entrada:

    $$c_t = f_t \odot c_{t-1} + i_t \odot g_t$$

    Donde $\odot$ denota la multiplicación elemento a elemento (producto Hadamard).

  3. Cálculo de la salida de la unidad:

    • Finalmente, se calcula la salida $h_t$ de la unidad LSTM utilizando la celda de memoria actual $c_t$ y la puerta de salida $o_t$:

    $$h_t = o_t \odot \tanh(c_t)$$

Estas operaciones matemáticas forman la base de una celda LSTM y permiten que la unidad capture y controle el flujo de información a lo largo del tiempo en una secuencia de datos.

drawing

Entrada y salida para una red LSTM¶

La creación de secuencias de entrada y salida para una red LSTM depende del tipo de problema que estés abordando. Aquí hay una descripción general de cómo puedes preparar tus datos para entrenar una LSTM:

  1. Definir el tamaño de la secuencia:

    • El primer paso es definir el tamaño de las secuencias de entrada y salida que se utilizarán para entrenar la LSTM. Esto determina cuántos pasos de tiempo pasados se utilizarán como entrada para predecir el siguiente paso de tiempo en la serie temporal.
  2. Dividir los datos en secuencias:

    • A continuación, debes dividir tus datos en secuencias de longitud adecuada. Por ejemplo, si estás trabajando con datos de series temporales, puedes dividir la serie temporal en secuencias de longitud fija, donde cada secuencia consiste en un conjunto de pasos de tiempo consecutivos.
  3. Crear conjuntos de entrada y salida:

    • Una vez que hayas dividido tus datos en secuencias, debes crear conjuntos de datos de entrada y salida. Para cada secuencia de entrada, la secuencia siguiente será la etiqueta de salida correspondiente.
    • Por ejemplo, si estás tratando de predecir el siguiente valor en una serie temporal, la secuencia de entrada será un conjunto de pasos de tiempo consecutivos y la secuencia de salida será el siguiente paso de tiempo en la serie.
  4. Preparar los datos para la red LSTM:

    • Antes de alimentar los datos a la red LSTM, es importante formatearlos en el formato adecuado. Esto generalmente implica convertir los datos en tensores de numpy o tensores de PyTorch, dependiendo de la biblioteca que estés utilizando.
    • Además, es posible que necesites ajustar la forma de los datos para que sean compatibles con la entrada de la red LSTM. Por lo general, la entrada de una LSTM es un tensor tridimensional con la forma (número de muestras, longitud de la secuencia, número de características).
  5. Dividir los datos en conjuntos de entrenamiento, validación y prueba:

    • Por último, es importante dividir tus datos en conjuntos de entrenamiento, validación y prueba para evaluar el rendimiento del modelo. Esto te permitirá entrenar el modelo en un conjunto de datos, ajustar los hiperparámetros en un conjunto de validación y evaluar el rendimiento final en un conjunto de prueba.

La creación de secuencias de entrada y salida para una red LSTM implica dividir tus datos en secuencias de longitud adecuada y crear conjuntos de entrada y salida correspondientes. Esto te permite entrenar la red LSTM para predecir el siguiente paso en una serie temporal u otros tipos de datos secuenciales.

In [1]:
import tensorflow as tf
from keras.layers import Dropout
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import LSTM
from tensorflow.keras.metrics import RootMeanSquaredError, MeanAbsoluteError
from tensorflow.keras.models import Sequential
import pandas as pd
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_percentage_error
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import r2_score
2024-02-26 16:09:53.662938: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
To enable the following instructions: SSE4.1 SSE4.2 AVX AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.

Modelos univariados¶

Vamos a modelar el valor típico TYPPRICE de la empresa Bajaj Finance.

El "TYPPRICE" de un stock se refiere al "precio típico" o "precio promedio ponderado" de un activo financiero, como una acción. Este indicador calcula un precio promedio utilizando el precio más alto, el más bajo y el precio de cierre de una acción en un determinado período de tiempo.

La fórmula para calcular el precio típico de una acción es:

$$TYPPRICE = \frac{High + Low + Close}{3}$$

Donde:

  • "High" es el precio más alto alcanzado por la acción en el período de tiempo.
  • "Low" es el precio más bajo alcanzado por la acción en el período de tiempo.
  • "Close" es el precio de cierre de la acción en el período de tiempo.

El precio típico se utiliza comúnmente en análisis técnico y en la elaboración de gráficos financieros para proporcionar una representación simplificada del precio medio de una acción en un período de tiempo específico. Ayuda a los analistas a tener una idea general del rendimiento de un activo y puede ser útil para identificar tendencias y patrones en los movimientos de precios.

Modelar el TYPPRICE (precio típico) utilizando series temporales tiene varios propósitos y aplicaciones:

  1. Predicción de precios futuros: Al modelar el TYPPRICE como una serie temporal, podemos utilizar técnicas de análisis de series temporales para predecir los movimientos futuros del precio típico de un activo financiero. Esto puede ayudar a los inversores y traders a tomar decisiones informadas sobre cuándo comprar, vender o mantener una acción.

  2. Identificación de patrones y tendencias: El análisis de series temporales del TYPPRICE puede ayudar a identificar patrones y tendencias en el comportamiento del precio típico de una acción a lo largo del tiempo. Esto puede proporcionar información valiosa sobre la dirección general del mercado y las oportunidades de inversión.

  3. Evaluación de la volatilidad y el riesgo: El modelado del TYPPRICE puede ayudar a medir la volatilidad y el riesgo asociados con un activo financiero. Al analizar la variabilidad y la distribución del precio típico a lo largo del tiempo, los inversores pueden evaluar mejor el riesgo y tomar decisiones más informadas sobre la gestión de su cartera.

  4. Optimización de estrategias comerciales: Los modelos de series temporales del TYPPRICE pueden utilizarse para optimizar estrategias comerciales, como el tiempo de entrada y salida del mercado, el establecimiento de límites de pérdidas y la determinación de objetivos de ganancias. Al utilizar métodos cuantitativos para analizar el comportamiento histórico del precio típico, los traders pueden mejorar la rentabilidad y reducir el riesgo de sus operaciones.

Modelar el TYPPRICE utilizando series temporales tiene como objetivo proporcionar información valiosa sobre el comportamiento pasado y futuro del precio típico de un activo financiero, lo que puede ayudar a los inversores y traders a tomar decisiones más informadas y rentables.

Ejemplo: Modelado del TYPPRICE Baja Finance¶

Un problema de modelado de series temporales del TYPPRICE (precio típico) de Bajaj Finance podría implicar la predicción del precio típico de las acciones de Bajaj Finance en el futuro utilizando datos históricos. Aquí hay una descripción detallada del problema:

  • Objetivo: El objetivo principal es desarrollar un modelo predictivo que pueda predecir con precisión el precio típico de las acciones de Bajaj Finance en un futuro período de tiempo basado en datos históricos.

  • Datos disponibles: Se dispone de datos históricos del precio de las acciones de Bajaj Finance, que incluyen el precio de apertura, el precio máximo, el precio mínimo, el precio de cierre y posiblemente otros datos relevantes como el volumen de operaciones, noticias del mercado, indicadores técnicos, etc. Estos datos se utilizarán para calcular el precio típico de las acciones.

  • Desafíos potenciales: Algunos desafíos potenciales en el modelado de esta serie temporal incluyen la volatilidad inherente en los precios de las acciones, la influencia de factores externos como eventos del mercado, noticias económicas y políticas, y la necesidad de lidiar con datos ruidosos o faltantes.

  • Enfoque de modelado: Se pueden utilizar diversas técnicas de modelado de series temporales, en particular modelos de redes neuronales recurrentes (RNN), LSTM (Long Short-Term Memory), GRU (Gated Recurrent Unit) u otros modelos de aprendizaje automático. También se pueden explorar enfoques de modelado más avanzados, como el uso de redes neuronales convolucionales (CNN) o modelos de atención.

  • Evaluación del modelo: El modelo desarrollado se evaluará utilizando métricas de rendimiento adecuadas para problemas de series temporales, como el error cuadrático medio (MSE), el error absoluto medio (MAE), el coeficiente de determinación (R$^2$), entre otros. Se puede utilizar la validación cruzada o la división de datos en conjuntos de entrenamiento y prueba para evaluar la capacidad de generalización del modelo.

Por tanto queremos modelar las series temporales del TYPPRICE de Bajaj Finance, lo que implica predecir el precio típico de las acciones en el futuro utilizando datos históricos, con el objetivo de desarrollar un modelo predictivo preciso que pueda ayudar a los inversores y traders a tomar decisiones informadas sobre sus inversiones.

In [2]:
df = pd.read_csv("https://raw.githubusercontent.com/marsgr6/ml-online/main/data/BAJFINANCE_day__with_indicators_.csv",
                 index_col=0, parse_dates=[0])
df.head()
Out[2]:
close high low open volume sma5 sma10 sma15 sma20 ema5 ... fastd fastksr fastdsr ULTOSC WILLR ATR Trange TYPPRICE HT_DCPERIOD BETA
date
2015-04-07 00:00:00+05:30 434.12 436.50 421.03 427.50 184490 416.608 406.580 405.340667 407.8915 419.508314 ... 87.187252 100.0 100.000000 53.806453 -5.131522 13.078931 15.47 430.550000 16.321340 -0.112344
2015-04-08 00:00:00+05:30 448.92 453.00 428.56 434.12 188240 426.398 411.542 408.568000 409.2630 429.312209 ... 92.191550 100.0 100.000000 61.430981 -6.488550 13.890436 24.44 443.493333 16.902700 -0.058786
2015-04-09 00:00:00+05:30 450.47 469.90 440.01 454.20 535180 434.322 416.908 411.706667 410.8740 436.364806 ... 85.344198 100.0 100.000000 57.072816 -24.354475 15.033262 29.89 453.460000 17.555276 0.099487
2015-04-10 00:00:00+05:30 452.21 459.80 447.88 453.00 324510 441.962 422.647 414.866667 412.8210 441.646537 ... 76.471842 100.0 100.000000 57.196769 -22.173477 14.810886 11.92 453.296667 18.449062 0.075360
2015-04-13 00:00:00+05:30 445.38 452.21 442.00 452.21 300820 446.220 427.619 417.655333 414.3925 442.891025 ... 62.265251 0.0 66.666667 56.118377 -31.516710 14.482252 10.21 446.530000 19.714991 0.317607

5 rows × 59 columns

In [3]:
plt.figure(figsize=(12,4))
df.TYPPRICE.plot()
Out[3]:
<Axes: xlabel='date'>
In [4]:
train, test = df.TYPPRICE[:-300], df.TYPPRICE[-300:]

plt.figure(figsize=(12,4))
plt.plot(train, label="Training")
plt.plot(test, label="Test")
plt.legend()
Out[4]:
<matplotlib.legend.Legend at 0x7f20d6471f10>

Deep learning: Biblioteca Keras¶

La biblioteca Keras es una biblioteca de código abierto escrita en Python que proporciona una API de alto nivel para construir y entrenar modelos de aprendizaje profundo. Fue desarrollada inicialmente por François Chollet y se ha convertido en una de las bibliotecas más populares para el desarrollo de redes neuronales debido a su simplicidad, modularidad y facilidad de uso.

Keras se diseñó con el objetivo de permitir a los usuarios crear rápidamente prototipos de modelos de aprendizaje profundo con una sintaxis simple y fácil de entender. Proporciona una interfaz consistente y fácil de usar que permite a los desarrolladores crear y entrenar redes neuronales con pocas líneas de código.

Las principales características de Keras incluyen:

  1. Simplicidad: Keras ofrece una API simple y coherente que permite a los desarrolladores crear modelos de aprendizaje profundo con facilidad. Está diseñado para ser accesible incluso para aquellos que no tienen experiencia previa en aprendizaje profundo.

  2. Modularidad: Keras está diseñado de manera modular, lo que significa que los usuarios pueden construir modelos complejos combinando capas y módulos predefinidos de manera flexible.

  3. Flexibilidad: Keras es compatible con varias bibliotecas de backend de aprendizaje profundo, incluyendo TensorFlow, Theano y Microsoft Cognitive Toolkit (CNTK). Esto permite a los usuarios elegir el backend que mejor se adapte a sus necesidades y utilizar Keras como una interfaz común.

  4. Facilidad de extensión: Keras permite a los usuarios definir y entrenar fácilmente sus propias capas, funciones de activación y funciones de pérdida personalizadas, lo que facilita la experimentación con nuevas ideas y algoritmos.

Keras es una biblioteca de aprendizaje profundo de alto nivel que facilita la creación y el entrenamiento de modelos de redes neuronales. Su simplicidad y modularidad lo hacen popular entre los investigadores, los estudiantes y los desarrolladores que desean construir modelos de aprendizaje profundo de manera rápida y eficiente.

Preparación de los datos¶

Los modelos realizan un conjunto de predicciones basadas en una ventana de muestras consecutivas de los datos.

Las principales características de las ventanas de entrada son:

  • El ancho (número de pasos de tiempo) de las ventanas de entrada (input width) y etiqueta (label width).
  • El desplazamiento de tiempo entre ellas (offset).
  • Qué características se utilizan como entradas, etiquetas o ambas.

De acuerdo al propósito del modelo podemos tener:

  • Predicciones de salida única y múltiple.
  • Predicciones de un solo paso de tiempo y múltiples pasos de tiempo. Esta sección se centra en implementar la ventana de datos para que pueda ser reutilizada para todos esos modelos.

Dependiendo de la tarea y el tipo de modelo, es posible que desees generar una variedad de ventanas de datos.

Por ejemplo, para hacer una única predicción 24 horas en el futuro, dadas 24 horas de historial, podrías definir una ventana así:

drawing

Un modelo que realiza una predicción una hora en el futuro, dadas seis horas de historial, necesitaría una ventana como esta:

drawing

Si necesita reutilizar el código tenga en cuenta estos parámetros para ajustarlos adecuadamente.

In [5]:
# univariate data preparation
# split a univariate sequence into samples
def split_sequence(sequence, n_steps):
    X, y = list(), list()
    for i in range(len(sequence)):
    # find the end of this pattern
        end_ix = i + n_steps
        # check if we are beyond the sequence
        if end_ix > len(sequence)-1: break
        # gather input and output parts of the pattern
        seq_x, seq_y = sequence[i:end_ix], sequence[end_ix]
        X.append(seq_x)
        y.append(seq_y)
    return np.array(X), np.array(y)


# choose a number of time steps
n_steps = 10

# split train into samples
X_train, y_train = split_sequence(train, n_steps)
X_train.shape, y_train.shape
Out[5]:
((1549, 10), (1549,))

Vanilla LSTM¶

Una Vanilla LSTM, o LSTM estándar, es una arquitectura básica de Red Neuronal Recurrente (RNN) que utiliza unidades LSTM (Long Short-Term Memory) para modelar secuencias de datos.

A continuación tienes un resumen de las características principales de una Vanilla LSTM:

  1. Arquitectura básica: Una Vanilla LSTM consta de una o más capas de unidades LSTM conectadas en serie.

  2. Unidades LSTM: Cada unidad LSTM tiene una estructura interna compleja que incluye puertas de control de flujo de información (puertas de olvido, puertas de entrada y puertas de salida) y una celda de memoria para mantener información a lo largo del tiempo.

  3. Control del flujo de información: Las puertas en una unidad LSTM controlan el flujo de información a través de la red. Por ejemplo, la puerta de olvido decide qué información de la celda de memoria anterior debe ser olvidada, la puerta de entrada decide qué nueva información debe ser almacenada en la celda de memoria, y la puerta de salida decide qué información de la celda de memoria se utilizará para calcular la salida de la unidad.

  4. Entrenamiento: Una Vanilla LSTM se entrena utilizando algoritmos de optimización como el descenso de gradiente estocástico (SGD) o sus variantes, donde se ajustan los pesos de las conexiones neuronales para minimizar una función de pérdida que mide la discrepancia entre las predicciones del modelo y las etiquetas reales.

  5. Aplicaciones: Las Vanilla LSTM se utilizan en una variedad de aplicaciones de procesamiento de secuencias, como el procesamiento del lenguaje natural (NLP), la traducción automática, la generación de texto, la predicción de series temporales y más.

Una Vanilla LSTM es una arquitectura básica de RNN que utiliza unidades LSTM para modelar dependencias temporales en los datos. Es una herramienta poderosa para el procesamiento de secuencias y se utiliza en una amplia gama de aplicaciones de aprendizaje automático.

El código a continuación utiliza la biblioteca Keras, que es una API de alto nivel para construir y entrenar modelos de aprendizaje profundo en TensorFlow.

Código¶

  1. n_features = 1: Esta línea define el número de características (o variables) en tus datos de entrada. En este caso, se indica que hay una única característica en tus datos de entrada.

  2. model = Sequential(): Aquí se crea un modelo secuencial, que es un tipo de modelo en Keras que permite construir capas una encima de la otra en una secuencia lineal.

  3. model.add(LSTM(50, activation='relu', input_shape=(n_steps, n_features))): Se agrega una capa LSTM al modelo. Los argumentos que se pasan a LSTM son:

    • 50: Número de unidades (o neuronas) en la capa LSTM. Esto significa que habrá 50 unidades LSTM en esta capa.
    • activation='relu': Función de activación ReLU (Rectified Linear Unit), que se utilizará en las unidades LSTM.
    • input_shape=(n_steps, n_features): Forma de la entrada de los datos. n_steps es el número de pasos de tiempo en cada secuencia y n_features es el número de características en cada paso de tiempo. En este caso, n_steps no está definido en el código proporcionado, pero es probable que se defina en otro lugar de tu código.
  4. model.add(Dense(1)): Después de la capa LSTM, se agrega una capa densa (fully connected) con una única neurona. Esta capa se utiliza para generar la salida del modelo.

  5. model.compile(optimizer='adam', loss='mse'): Se compila el modelo utilizando el optimizador Adam y la función de pérdida de error cuadrático medio (MSE, Mean Squared Error). La compilación del modelo configura el modelo para el entrenamiento, definiendo la función de pérdida y el optimizador que se utilizará durante el entrenamiento.

In [6]:
# define model
n_features = 1
model = Sequential()
model.add(LSTM(50, activation='relu', input_shape=(n_steps, n_features)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
2024-02-26 16:10:07.136624: I tensorflow/core/common_runtime/process_util.cc:146] Creating new thread pool with default inter op setting: 2. Tune using inter_op_parallelism_threads for best performance.
In [7]:
# reshape from [samples, timesteps] into [samples, timesteps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], n_features))
X_train.shape
Out[7]:
(1549, 10, 1)
In [8]:
# fit model
model.fit(X_train, y_train, epochs=200, verbose=0)
Out[8]:
<keras.callbacks.History at 0x7f20d63249d0>
In [9]:
# demonstrate prediction
X_test, y_test = split_sequence(test, n_steps)
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], n_features))
yhat = model.predict(X_test, verbose=0)
yhat.shape
Out[9]:
(290, 1)
In [10]:
plt.figure(figsize=(12,4))
plt.plot(train, label="Training")
plt.plot(test, label="Test")
plt.plot(test.index[n_steps:], yhat, label="Predicted")
plt.xlim(df.index[-300],df.index[-1])
plt.ylim(5200, 8000)
plt.legend()

print("R2:", r2_score(test[n_steps:], yhat))
print("MAPE:", mean_absolute_percentage_error(test[n_steps:], yhat))
print("MAE:", mean_absolute_error(test[n_steps:], yhat))
print("MSE:", mean_squared_error(test[n_steps:], yhat))
print("RMSE:", np.sqrt(mean_squared_error(test[n_steps:], yhat)))
R2: 0.9208569462487023
MAPE: 0.020648673014497843
MAE: 141.59403129489945
MSE: 35233.47607931541
RMSE: 187.70582324295486

Normalización de datos¶

La normalización de datos es una práctica común en el entrenamiento de redes neuronales, incluidas las redes LSTM (Long Short-Term Memory), aunque no es estrictamente obligatoria. La normalización puede ayudar a acelerar el entrenamiento y mejorar la convergencia del modelo, especialmente cuando los datos tienen diferentes escalas o varianzas.

Se listan a continuación algunas razones por las cuales la normalización puede ser beneficiosa para el entrenamiento de una LSTM:

  1. Estabilidad del entrenamiento: La normalización de los datos puede ayudar a estabilizar el proceso de entrenamiento al mantener los valores de entrada dentro de un rango razonable, lo que evita la saturación de las funciones de activación y el desvanecimiento del gradiente.

  2. Velocidad de convergencia: Al tener datos normalizados, es más probable que los gradientes generados durante el entrenamiento tengan magnitudes consistentes, lo que puede acelerar la convergencia del modelo.

  3. Regularización: La normalización puede actuar como una forma de regularización al imponer una escala común a los datos, lo que puede ayudar a prevenir el sobreajuste al reducir la dependencia de los valores de los parámetros del modelo en la escala absoluta de los datos.

Sin embargo, es importante tener en cuenta que la normalización debe realizarse de manera cuidadosa y apropiada para el problema específico que estés abordando. Además, la necesidad de normalización puede variar según la naturaleza de los datos y la arquitectura de la red neuronal utilizada. En algunos casos, como en aplicaciones de series temporales, la normalización puede ser especialmente importante debido a la sensibilidad de las redes LSTM a las variaciones en la escala de los datos a lo largo del tiempo.

Aunque la normalización no es estrictamente necesaria para el entrenamiento de una LSTM, puede ser beneficiosa en términos de estabilidad, velocidad de convergencia y regularización. Sin embargo, es importante experimentar con diferentes enfoques y evaluar cómo afecta la normalización al rendimiento del modelo en tu conjunto de datos específico.

In [11]:
# normalization on train data
scaler = MinMaxScaler(feature_range=(0, 1))
values = train.values.reshape(-1,1)

# scale exclusively on train set
# the idea is that you don't have access to test data
scaler = scaler.fit(values)

train_norm = scaler.transform(values)

values = test.values.reshape(-1,1)

test_norm = scaler.transform(values)
In [12]:
# define model
model = Sequential()
model.add(LSTM(50, activation='relu', input_shape=(n_steps, n_features)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
In [13]:
# choose a number of time steps
n_steps = 10

# split train into samples
X_train, y_train = split_sequence(train_norm, n_steps)
X_train.shape, y_train.shape

# reshape from [samples, timesteps] into [samples, timesteps, features]
n_features = 1
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], n_features))
X_train.shape
Out[13]:
(1549, 10, 1)
In [14]:
# fit model
model.fit(X_train, y_train, epochs=200, verbose=0)
Out[14]:
<keras.callbacks.History at 0x7f20cc4e2510>
In [15]:
# demonstrate prediction
X_test, y_test = split_sequence(test_norm, n_steps)
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], n_features))

# predict
yhat = model.predict(X_test, verbose=0)

# inverse transform output of the model
yhat = scaler.inverse_transform(yhat)
yhat.shape
Out[15]:
(290, 1)
In [16]:
plt.figure(figsize=(12,4))
plt.plot(train, label="Training")
plt.plot(test, label="Test")
plt.plot(test.index[n_steps:], yhat, label="Predicted")
plt.xlim(df.index[-300],df.index[-1])
plt.ylim(5200, 8000)
plt.legend()

print("R2:", r2_score(test[n_steps:], yhat))
print("MAPE:", mean_absolute_percentage_error(test[n_steps:], yhat))
print("MAE:", mean_absolute_error(test[n_steps:], yhat))
print("MSE:", mean_squared_error(test[n_steps:], yhat))
print("RMSE:", np.sqrt(mean_squared_error(test[n_steps:], yhat)))
R2: 0.9676956394116021
MAPE: 0.01288274885192502
MAE: 88.6492976607399
MSE: 14381.48848319156
RMSE: 119.92284387551673

Más capas (Stacked LSTM)¶

Un modelo Stacked LSTM es una arquitectura de red neuronal recurrente (RNN) que consta de múltiples capas de unidades LSTM (Long Short-Term Memory) apiladas una encima de la otra. En un modelo Stacked LSTM, las capas LSTM se conectan en serie, de modo que la salida de una capa LSTM se utiliza como entrada para la siguiente capa LSTM.

Las características principales de un modelo Stacked LSTM son las siguientes:

  1. Múltiples capas LSTM: Un modelo Stacked LSTM consta de dos o más capas LSTM apiladas verticalmente. Cada capa LSTM tiene su propia celda de memoria y puertas de control de flujo de información (puertas de olvido, puertas de entrada y puertas de salida) para capturar y procesar la información a lo largo de la secuencia de entrada.

  2. Profundidad de la red: La profundidad de la red se refiere al número de capas LSTM apiladas en el modelo. Un mayor número de capas LSTM puede ayudar al modelo a capturar patrones de dependencias temporales más complejos en los datos, lo que puede conducir a un mejor rendimiento en tareas de predicción secuencial.

  3. Aprendizaje jerárquico de características: Al tener múltiples capas LSTM, un modelo Stacked LSTM puede aprender representaciones jerárquicas de las características en los datos, donde las capas superiores pueden aprender características más abstractas o de alto nivel basadas en las características extraídas por las capas inferiores.

  4. Mayor capacidad de representación: La arquitectura Stacked LSTM puede proporcionar una mayor capacidad de representación en comparación con una sola capa LSTM, lo que puede ser beneficioso para modelar datos secuenciales complejos o para tareas que requieren una mayor capacidad de memoria a largo plazo.

Un modelo Stacked LSTM es una arquitectura de red neuronal recurrente que utiliza múltiples capas LSTM apiladas verticalmente para capturar patrones de dependencias temporales en datos secuenciales. Esta arquitectura es útil para modelar secuencias de datos complejas y puede proporcionar una mayor capacidad de representación en comparación con una sola capa LSTM.

In [17]:
# define model
model = Sequential()
model.add(LSTM(50, activation='relu', return_sequences=True, input_shape=(n_steps, n_features)))
model.add(LSTM(50, activation='relu'))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
In [18]:
# fit model
model.fit(X_train, y_train, epochs=200, verbose=0)

# demonstrate prediction
yhat = model.predict(X_test, verbose=0)

# inverse transform output of the model
yhat = scaler.inverse_transform(yhat)
In [19]:
plt.figure(figsize=(12,4))
plt.plot(train, label="Training")
plt.plot(test, label="Test")
plt.plot(test.index[n_steps:], yhat, label="Predicted")
plt.xlim(df.index[-300],df.index[-1])
plt.ylim(5200, 8000)
plt.legend()

print("R2:", r2_score(test[n_steps:], yhat))
print("MAPE:", mean_absolute_percentage_error(test[n_steps:], yhat))
print("MAE:", mean_absolute_error(test[n_steps:], yhat))
print("MSE:", mean_squared_error(test[n_steps:], yhat))
print("RMSE:", np.sqrt(mean_squared_error(test[n_steps:], yhat)))
R2: 0.9464719010474469
MAPE: 0.01762395892605573
MAE: 123.27349126706184
MSE: 23830.02556285721
RMSE: 154.36976894086877

Bidirectional LSTM¶

Una Bidirectional LSTM es una variante de la arquitectura LSTM (Long Short-Term Memory) que procesa la secuencia de entrada tanto en dirección hacia adelante como en dirección hacia atrás. Esto significa que la red neuronal tiene dos capas LSTM: una que procesa la secuencia en orden temporal desde el principio hasta el final (forward LSTM), y otra que procesa la secuencia en orden inverso, desde el final hasta el principio (backward LSTM). Las salidas de estas dos capas se concatenan o combinan de alguna manera para producir la salida final de la red.

Las principales características de una Bidirectional LSTM son las siguientes:

  1. Modelado de contexto bidireccional: Al procesar la secuencia tanto en dirección hacia adelante como hacia atrás, una Bidirectional LSTM es capaz de capturar información contextual de ambos lados de cada punto en la secuencia. Esto permite que la red neuronal tenga en cuenta tanto el pasado como el futuro al hacer predicciones para cada punto de la secuencia.

  2. Captura de patrones complejos: Al tener en cuenta el contexto tanto hacia adelante como hacia atrás, una Bidirectional LSTM puede capturar patrones temporales más complejos en los datos. Esto puede ser especialmente útil en tareas donde la información relevante para hacer una predicción puede estar distribuida en ambas direcciones de la secuencia.

  3. Mayor capacidad de modelado: La arquitectura bidireccional permite que la red neuronal tenga una mayor capacidad de modelado en comparación con una LSTM unidireccional estándar, ya que puede aprovechar toda la información disponible en la secuencia de entrada.

  4. Aplicaciones en NLP y secuencias de tiempo: Las Bidirectional LSTM son populares en tareas de procesamiento del lenguaje natural (NLP) y en el análisis de series temporales, donde la captura de información contextual bidireccional puede mejorar significativamente el rendimiento del modelo.

Una Bidirectional LSTM es una variante de la arquitectura LSTM que procesa la secuencia de entrada tanto en dirección hacia adelante como hacia atrás, permitiendo que la red capture información contextual bidireccional y capturar patrones complejos en los datos.

In [20]:
from keras.layers import Bidirectional

# define model
model = Sequential()
model.add(Bidirectional(LSTM(50, activation='relu'), input_shape=(n_steps, n_features)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
In [21]:
# fit model
model.fit(X_train, y_train, epochs=200, verbose=0)

# demonstrate prediction
yhat = model.predict(X_test, verbose=0)

# inverse transform output of the model
yhat = scaler.inverse_transform(yhat)
In [22]:
plt.figure(figsize=(12,4))
plt.plot(train, label="Training")
plt.plot(test, label="Test")
plt.plot(test.index[n_steps:], yhat, label="Predicted")
plt.xlim(df.index[-300],df.index[-1])
plt.ylim(5200, 8000)
plt.legend()

print("R2:", r2_score(test[n_steps:], yhat))
print("MAPE:", mean_absolute_percentage_error(test[n_steps:], yhat))
print("MAE:", mean_absolute_error(test[n_steps:], yhat))
print("MSE:", mean_squared_error(test[n_steps:], yhat))
print("RMSE:", np.sqrt(mean_squared_error(test[n_steps:], yhat)))
R2: 0.9673425472189747
MAPE: 0.012896920631068984
MAE: 88.72043159572557
MSE: 14538.680614819734
RMSE: 120.57645132785976

CNN LSTM¶

Una CNN-LSTM es una arquitectura híbrida que combina características de las redes neuronales convolucionales (CNN) y las redes neuronales recurrentes (RNN), específicamente las Long Short-Term Memory (LSTM). Esta combinación se utiliza principalmente para modelar datos secuenciales bidimensionales, como imágenes o datos de series temporales con información espacial.

Funcionamiento de una CNN-LSTM:

  1. Extracción de características con CNN: La primera parte de la red consiste en una o más capas de CNN, que se utilizan para extraer características relevantes de la entrada bidimensional, como una imagen. Las capas convolucionales aplican filtros a la entrada para detectar patrones locales y crear representaciones de características.

  2. Procesamiento secuencial con LSTM: La salida de la última capa de la CNN se pasa a una o más capas LSTM. En lugar de procesar la entrada en paralelo como las CNN, las LSTM procesan la entrada secuencialmente, manteniendo y actualizando una memoria a largo plazo y una memoria a corto plazo para capturar dependencias temporales en los datos.

  3. Combinación de características espaciales y temporales: La salida de las capas LSTM representa las características aprendidas de manera secuencial, teniendo en cuenta la información temporal en los datos. Estas características se combinan con las características espaciales extraídas por la CNN para formar una representación combinada de la entrada.

  4. Predicción o clasificación: Finalmente, la representación combinada se pasa a una capa de salida, que puede ser una capa totalmente conectada para tareas de clasificación o una capa de regresión para tareas de predicción. Esta capa genera la salida final del modelo, que puede ser una etiqueta de clase, un valor numérico u otra forma de salida deseada.

Una CNN-LSTM es una arquitectura híbrida que combina la capacidad de las redes neuronales convolucionales para extraer características espaciales con la capacidad de las redes neuronales recurrentes, específicamente las LSTM, para capturar dependencias temporales en los datos. Esta combinación es especialmente útil para modelar datos secuenciales bidimensionales, como imágenes o datos de series temporales con información espacial.

Ejemplo¶

Para el ejemplo que estamos trabajando, el primer paso es dividir las secuencias de entrada en subsecuencias que puedan ser procesadas por el modelo de CNN. Por ejemplo, podemos dividir primero nuestros datos de series temporales univariables en muestras de entrada/salida con cuatro pasos como entrada y uno como salida. Luego, cada muestra puede dividirse en dos submuestras, cada una con dos pasos temporales. La CNN puede interpretar cada subsecuencia de dos pasos temporales y proporcionar una serie temporal de interpretaciones de las subsecuencias al modelo de LSTM para procesar como entrada.

Queremos reutilizar el mismo modelo de CNN al leer cada subsecuencia de datos por separado.

Esto se puede lograr envolviendo todo el modelo de CNN en un contenedor TimeDistributed que aplicará el modelo completo una vez por entrada, en este caso, una vez por subsecuencia de entrada.

El modelo de CNN primero tiene una capa de convolución para leer a través de la subsecuencia que requiere que se especifiquen un número de filtros y un tamaño de kernel. El número de filtros es la cantidad de lecturas o interpretaciones de la secuencia de entrada. El tamaño del kernel es la cantidad de pasos de tiempo incluidos en cada operación de "lectura" de la secuencia de entrada.

La capa de convolución es seguida por una capa de agrupación máxima que reduce los mapas de filtros a la mitad de su tamaño, lo que incluye las características más relevantes. Estas estructuras luego se aplanan en un vector unidimensional único para ser utilizado como un único paso de tiempo de entrada a la capa LSTM.

# define model
model = Sequential()
model.add(TimeDistributed(Conv1D(filters=64, kernel_size=1, activation='relu'), input_shape=(None, n_steps, n_features)))
model.add(TimeDistributed(MaxPooling1D(pool_size=2)))
model.add(TimeDistributed(Flatten()))

A continuación, podemos definir la parte LSTM del modelo que interpreta la lectura del modelo de CNN de la secuencia de entrada y realiza una predicción.

model.add(LSTM(50, activation='relu'))
model.add(Dense(1))

Podemos unir todo esto; el ejemplo completo de un modelo CNN-LSTM para pronóstico de series temporales univariables se muestra a continuación.

La arquitectura del modelo proporcionado es una combinación de capas convolucionales y recurrentes, específicamente diseñada para procesar secuencias de datos.

Aquí está la descripción de cada capa en el modelo:

  1. TimeDistributed(Conv1D):

    • Esta es una capa convolucional unidimensional (Conv1D) aplicada a cada paso de tiempo de una secuencia.
    • El parámetro filters=64 especifica que se utilizarán 64 filtros en la convolución.
    • El parámetro kernel_size=1 indica que se utilizará un tamaño de kernel de 1 en la convolución.
    • La función de activación ReLU se aplica después de la convolución.
    • input_shape=(None, n_steps, n_features) especifica la forma de entrada de los datos. Aquí, None indica que la longitud de la secuencia puede variar, n_steps es el número de pasos de tiempo en cada secuencia, y n_features es el número de características en cada paso de tiempo.
  2. TimeDistributed(MaxPooling1D):

    • Esta es una capa de agrupación unidimensional (MaxPooling1D) aplicada a cada paso de tiempo después de la convolución.
    • pool_size=2 especifica el tamaño de la ventana de agrupación como 2, lo que significa que se tomará el máximo valor en ventanas de tamaño 2.
  3. TimeDistributed(Flatten):

    • Esta es una capa de aplanamiento aplicada a cada paso de tiempo después de la agrupación.
    • Su función es convertir el tensor tridimensional resultante de la convolución y la agrupación en un tensor bidimensional para que pueda ser alimentado a la capa LSTM.
  4. LSTM:

    • Esta es una capa LSTM (Long Short-Term Memory), que es una capa recurrente diseñada para modelar secuencias de datos.
    • units=50 especifica el número de unidades LSTM en la capa.
    • La función de activación ReLU se aplica después de la LSTM.
  5. Dense:

    • Esta es una capa densa (totalmente conectada) con una sola neurona de salida.
    • No se especifica ninguna función de activación, lo que implica que se utilizará una salida lineal (por defecto).

Este modelo combina capas convolucionales, de agrupación y recurrentes (LSTM) para procesar secuencias de datos y realizar una predicción de regresión. La arquitectura permite extraer características relevantes de las secuencias de entrada y modelar relaciones temporales entre los pasos de tiempo.

In [23]:
from keras.layers import Flatten
from keras.layers import TimeDistributed
from tensorflow.keras.layers import Conv1D
from tensorflow.keras.layers import MaxPooling1D

# choose a number of time steps
n_steps = 4

# split train into samples
X_train, y_train = split_sequence(train_norm, n_steps)

# reshape from [samples, timesteps] into [samples, subsequences, timesteps, features]
n_features = 1
n_seq = 2
n_steps = 2
X_train = X_train.reshape((X_train.shape[0], n_seq, n_steps, n_features))
# define model
model = Sequential()
model.add(TimeDistributed(Conv1D(filters=64, kernel_size=1, activation='relu'), input_shape=(None, n_steps, n_features)))
model.add(TimeDistributed(MaxPooling1D(pool_size=2)))
model.add(TimeDistributed(Flatten()))
model.add(LSTM(50, activation='relu'))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')

# fit model
model.fit(X_train, y_train, epochs=500, verbose=0)
Out[23]:
<keras.callbacks.History at 0x7f20c42a2510>
In [24]:
# demonstrate prediction
n_steps = 4
X_test, y_test = split_sequence(test_norm, n_steps)

n_steps = 2
X_test = X_test.reshape((X_test.shape[0], n_seq, n_steps, n_features))
yhat = model.predict(X_test, verbose=0)

# inverse transform output of the model
yhat = scaler.inverse_transform(yhat)

n_steps = 4

plt.figure(figsize=(12,4))
plt.plot(train, label="Training")
plt.plot(test, label="Test")
plt.plot(test.index[n_steps:], yhat, label="Predicted")
plt.xlim(df.index[-300],df.index[-1])
plt.ylim(5200, 8000)
plt.legend()

print("R2:", r2_score(test[n_steps:], yhat))
print("MAPE:", mean_absolute_percentage_error(test[n_steps:], yhat))
print("MAE:", mean_absolute_error(test[n_steps:], yhat))
print("MSE:", mean_squared_error(test[n_steps:], yhat))
print("RMSE:", np.sqrt(mean_squared_error(test[n_steps:], yhat)))
R2: 0.8973511184414218
MAPE: 0.025057058348552236
MAE: 176.85128613721147
MSE: 45362.93595183944
RMSE: 212.98576466947137
In [25]:
# univariate convlstm example
from numpy import array
from keras.models import Sequential
from keras.layers import LSTM
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import ConvLSTM2D

# choose a number of time steps
n_steps = 4
# split into samples
X_train, y_train = split_sequence(train_norm, n_steps)
# reshape from [samples, timesteps] into [samples, timesteps, rows, columns, features]
n_features = 1
n_seq = 2
n_steps = 2
X_train = X_train.reshape((X_train.shape[0], n_seq, 1, n_steps, n_features))
# define model
model = Sequential()
model.add(ConvLSTM2D(filters=64, kernel_size=(1,2), activation='relu', input_shape=(n_seq, 1, n_steps, n_features)))
model.add(Flatten())
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
# fit model
model.fit(X_train, y_train, epochs=500, verbose=0)
Out[25]:
<keras.callbacks.History at 0x7f20bc4011d0>
In [26]:
# demonstrate prediction
n_steps = 4
X_test, y_test = split_sequence(test_norm, n_steps)

n_features = 1
n_seq = 2
n_steps = 2
X_test = X_test.reshape((X_test.shape[0], n_seq, 1, n_steps, n_features))
yhat = model.predict(X_test, verbose=0)

# inverse transform output of the model
yhat = scaler.inverse_transform(yhat)

n_steps = 4

plt.figure(figsize=(12,4))
plt.plot(train, label="Training")
plt.plot(test, label="Test")
plt.plot(test.index[n_steps:], yhat, label="Predicted")
plt.xlim(df.index[-300],df.index[-1])
plt.ylim(5200, 8000)
plt.legend()

print("R2:", r2_score(test[n_steps:], yhat))
print("MAPE:", mean_absolute_percentage_error(test[n_steps:], yhat))
print("MAE:", mean_absolute_error(test[n_steps:], yhat))
print("MSE:", mean_squared_error(test[n_steps:], yhat))
print("RMSE:", np.sqrt(mean_squared_error(test[n_steps:], yhat)))
R2: 0.96456380701713
MAPE: 0.013307140069478024
MAE: 91.58658821174687
MSE: 15660.08054107841
RMSE: 125.14024349136616

Modelos multivariados¶

En series temporales multivariadas para LSTM, nos enfrentamos a problemas donde hay dos o más series temporales de entrada paralelas y una serie temporal de salida que depende de las series temporales de entrada.

Las series temporales de entrada son paralelas porque cada serie tiene una observación en los mismos pasos de tiempo.

Las LSTM multivariadas son una extensión de las LSTM univariadas que pueden manejar múltiples variables de entrada en lugar de una sola variable de entrada. En otras palabras, en lugar de tener una única serie temporal como entrada, tenemos varias series temporales que representan diferentes variables observadas en el mismo intervalo de tiempo.

Algunas características clave de las LSTM multivariadas son las siguientes:

  1. Múltiples variables de entrada: En lugar de una sola variable de entrada, las LSTM multivariadas reciben múltiples series temporales como entrada. Cada serie temporal representa una variable diferente, como la temperatura, la humedad, la presión, etc.

  2. Modelado de interacciones entre variables: Al tener múltiples variables de entrada, las LSTM multivariadas pueden capturar relaciones y dependencias complejas entre las diferentes variables. Esto permite que el modelo tenga en cuenta cómo una variable puede influir en el comportamiento de las otras variables a lo largo del tiempo.

  3. Preprocesamiento de datos: Antes de alimentar los datos a la LSTM multivariada, es importante preprocesar los datos para asegurarse de que todas las variables estén en la misma escala y tengan una distribución similar. Esto puede incluir la normalización de las variables y la eliminación de valores atípicos.

  4. Arquitectura de red neuronal: La arquitectura de una LSTM multivariada es similar a la de una LSTM univariada, pero con múltiples series temporales de entrada. La red neuronal tiene una capa LSTM como capa principal, seguida opcionalmente de capas adicionales para procesamiento y predicción.

Las LSTM multivariadas son una extensión de las LSTM univariadas que pueden manejar múltiples variables de entrada. Estas redes son útiles para modelar y predecir series temporales donde múltiples factores influyen en el comportamiento observado.

Ejemplo: predicción de velocidad promedio de viento¶

Predecir la velocidad del viento es importante para la generación eléctrica por varias razones:

  1. Optimización de la producción de energía eólica: La velocidad del viento es un factor crucial que determina la cantidad de energía que se puede generar a partir de una turbina eólica. Conocer con precisión la velocidad del viento permite optimizar la operación de las turbinas eólicas para maximizar la producción de energía.

  2. Planificación y gestión de la red eléctrica: Las predicciones precisas de la velocidad del viento son esenciales para la planificación y gestión efectiva de la red eléctrica. Los operadores de la red necesitan anticipar con precisión la producción de energía eólica para garantizar un suministro de energía confiable y eficiente.

  3. Integración de energía renovable: La energía eólica es una fuente de energía renovable clave que contribuye a reducir las emisiones de carbono y mitigar el cambio climático. La predicción precisa de la velocidad del viento facilita la integración de la energía eólica en la red eléctrica, lo que ayuda a reducir la dependencia de los combustibles fósiles y promueve la transición hacia una matriz energética más sostenible.

  4. Reducción de costos y mejora de la eficiencia: Al predecir con precisión la velocidad del viento, los operadores de parques eólicos pueden optimizar la programación de mantenimiento y reducir los costos operativos. Además, una mejor predicción del viento permite una gestión más eficiente de la energía, lo que puede llevar a una mayor eficiencia en la generación eléctrica y ahorros económicos significativos.

Predecir la velocidad del viento es fundamental para la generación eléctrica debido a su impacto en la producción de energía eólica, la planificación de la red eléctrica, la integración de energía renovable y la eficiencia operativa y económica en general.

Proceso¶

Variable a predecir (output, $y$): WS1HA, que corresponde a la velocidad promedio de viento.

  • Problema de regresión: trataremos nuestros datos como un problema de regresión y usaremos Random Forest para identificar las variables más importantes.
    • Usaremos las 4 variables más importantes para modelar nuestra serie temporal multivariada.
In [27]:
df = pd.read_csv("https://raw.githubusercontent.com/marsgr6/ml-online/main/data/final_test_2015.csv",
                 index_col=0, parse_dates=[0])
df.head()
Out[27]:
SRGLOAVG1H SRGLOMAX1H SRGLOMIN1H TAAVG1H TAMAX1H TAMIN1H RHAVG1H RHMAX1H RHMIN1H PRSUM1H PAAVG1H PAMAX1H PAMIN1H WS1HA WS1HX WS1HM WD1HA
Local_Time
2016-08-07 20:00:00 0.0 0.0 0.0 13.2 14.6 12.1 76.0 83.0 66.0 0.0 743.8 744.2 743.6 3.2 6.4 0.3 45
2016-08-07 21:00:00 0.0 0.0 0.0 12.9 13.5 12.4 78.0 81.0 73.0 0.0 744.6 745.0 744.2 3.0 6.2 0.0 50
2016-08-07 22:00:00 0.0 0.0 0.0 12.7 12.9 12.4 80.0 83.0 77.0 0.0 745.1 745.3 744.9 3.1 5.8 0.4 52
2016-08-07 23:00:00 0.0 0.0 0.0 12.3 12.6 12.0 84.0 85.0 82.0 0.0 745.3 745.4 745.2 4.2 6.5 0.5 45
2016-08-08 00:00:00 0.0 0.0 0.0 12.1 12.4 12.0 84.0 86.0 82.0 0.0 745.3 745.4 745.1 4.0 6.3 0.5 49
In [28]:
from sklearn.ensemble import RandomForestRegressor

# Create a Random Forest Regressor with default parameters
clf = RandomForestRegressor()

X, y = df.drop(columns=['WS1HA']), np.array(df[['WS1HA']]).ravel()

# Train the model using the whole dataset and default parameters
clf.fit(X, y)

feature_scores = pd.Series(clf.feature_importances_, index=X.columns).sort_values(ascending=False)

top_features = 5

f, ax = plt.subplots(figsize=(5, 7))
ax = sns.barplot(x=feature_scores[:top_features], y=feature_scores.index[:top_features])
ax.set_yticklabels(feature_scores.index[:top_features])
ax.set_xlabel("Feature importance score")
ax.set_ylabel("Features")
ax.set_title("Ten (10) most important features")
plt.show()
In [29]:
feature_scores.index[:top_features]
Out[29]:
Index(['WS1HX', 'WS1HM', 'WD1HA', 'TAMAX1H', 'RHMIN1H'], dtype='object')

Feature selection¶

  • Seleccionamos las variables más importantes de acuerdo a RF.

  • Variables de entrada:

    • WS1HX: Velocidad de viento máxima (Km/s).
    • WS1HM: Velocidad de viento mínima (Km/s).
    • WD1HA: Dirección de viendo promedio (grados, 0-360).
    • TAMAX1H: Temperatura máxima (°C).
    • WS1HA: Velocidad de viento promedio (Km/s).

Todas los variables son horarias.

In [30]:
dfm = df[['WS1HX', 'WS1HM', 'WD1HA', 'TAMAX1H', 'WS1HA']]
dfm.describe()
Out[30]:
WS1HX WS1HM WD1HA TAMAX1H WS1HA
count 3509.000000 3509.000000 3509.000000 3509.000000 3509.000000
mean 5.992647 0.173953 121.383585 12.931376 2.574494
std 3.508640 0.341695 90.597937 4.797774 1.842728
min 0.600000 0.000000 2.000000 -5.400000 0.000000
25% 3.000000 0.000000 45.000000 10.100000 1.100000
50% 5.500000 0.000000 64.000000 12.100000 2.300000
75% 8.600000 0.300000 229.000000 16.800000 3.700000
max 17.400000 2.500000 359.000000 24.400000 8.600000
  • Dejamos por fuera de la visualización de las cajas la dirección de viento WD1HA dado que se encuentra en una escala que no nos permitiría observar las otras variables.
In [31]:
dfm.drop(columns='WD1HA').boxplot(figsize=(8,4))
Out[31]:
<Axes: >
In [32]:
sns.heatmap(dfm.corr(), annot=True, square=True)
Out[32]:
<Axes: >

Series temporales¶

  • Dirección de viento está en una escala mayor que no permite observar las otras series temporales.
In [33]:
dfm.resample('D').mean().plot(figsize=(12,4))
Out[33]:
<Axes: xlabel='Local_Time'>

Escalado de datos¶

In [34]:
scaler_X = MinMaxScaler(feature_range=(0, 1))
scaler_y = MinMaxScaler(feature_range=(0, 1))

train, test = dfm[:-300], dfm[-300:]

dataset = train.drop(columns="WS1HA")

scaler_X = scaler_X.fit(dataset)

train_norm = pd.DataFrame(scaler_X.transform(dataset),
                          columns=dataset.columns, index=dataset.index)

scaler_y = scaler_y.fit(train[["WS1HA"]])

train_norm["WS1HA"] = pd.DataFrame(scaler_y.transform(train[["WS1HA"]]),
                                index=train.index)

dataset = test.drop(columns="WS1HA")

test_norm = pd.DataFrame(scaler_X.transform(dataset),
                         columns=dataset.columns, index=dataset.index)
test_norm["WS1HA"] = pd.DataFrame(scaler_y.transform(test[["WS1HA"]]),
                                index=test.index)

train_norm.resample("D").mean().plot(figsize=(16,4))
Out[34]:
<Axes: xlabel='Local_Time'>
In [35]:
from keras.models import Sequential
from keras.layers import LSTM
from keras.layers import Dense

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps):
    X, y = list(), list()
    for i in range(len(sequences)):
        # find the end of this pattern
        end_ix = i + n_steps
        # check if we are beyond the dataset
        if end_ix > len(sequences):
            break
        # gather input and output parts of the pattern
        seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1, -1]
        X.append(seq_x)
        y.append(seq_y)
    return np.array(X), np.array(y)

# define input (first columns) and output (last column) sequences
# horizontally stack columns
dataset = np.hstack([train_norm[[col]] for col in train_norm.columns])
dataset.shape
Out[35]:
(3209, 5)
In [36]:
# choose a number of time steps
n_steps = 3
# convert into input/output
X, y = split_sequences(dataset, n_steps)
# the dataset knows the number of features, e.g. 2
n_features = X.shape[2]
# define model
model = Sequential()
model.add(LSTM(50, activation='relu', input_shape=(n_steps, n_features)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
# fit model
model.fit(X, y, epochs=200, verbose=0)
Out[36]:
<keras.callbacks.History at 0x7f206fb9fc50>
In [37]:
# demonstrate prediction
dataset = np.hstack([test_norm[[col]] for col in test_norm.columns])
X_test, y_test = split_sequences(dataset, n_steps)

yhat = model.predict(X_test, verbose=0)

# inverse transform output of the model
yhat = scaler_y.inverse_transform(yhat)
y_test = scaler_y.inverse_transform(pd.DataFrame(y_test))

plt.figure(figsize=(12,4))
plt.plot(y_test, label="Test")
plt.plot(yhat, label="Prediction")
plt.legend()

print("R2:", r2_score(y_test, yhat))
print("MAPE:", mean_absolute_percentage_error(y_test, yhat))
print("MAE:", mean_absolute_error(y_test, yhat))
print("MSE:", mean_squared_error(y_test, yhat))
print("RMSE:", np.sqrt(mean_squared_error(y_test, yhat)))
R2: 0.9344885388199896
MAPE: 0.2630396488410087
MAE: 0.32017993435979314
MSE: 0.19717855793552017
RMSE: 0.4440479230167845

Modelo univariado multi-step¶

In [38]:
# univariate multi-step vector-output stacked lstm example
from numpy import array
from keras.models import Sequential
from keras.layers import LSTM
from keras.layers import Dense

# split a univariate sequence into samples
def split_sequence(sequence, n_steps_in, n_steps_out):
    X, y = list(), list()
    for i in range(len(sequence)):
        # find the end of this pattern
        end_ix = i + n_steps_in
        out_end_ix = end_ix + n_steps_out
        # check if we are beyond the sequence
        if out_end_ix > len(sequence):
            break
        # gather input and output parts of the pattern
        seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix]
        X.append(seq_x)
        y.append(seq_y)
    return array(X), array(y)

# define input sequence
raw_seq = np.array(train_norm[["WS1HA"]])  # Modelamos Avg Wind Speed
# choose a number of time steps
n_steps_in, n_steps_out = 12, 4
# split into samples
X, y = split_sequence(raw_seq, n_steps_in, n_steps_out)
# reshape from [samples, timesteps] into [samples, timesteps, features]
n_features = 1
X = X.reshape((X.shape[0], X.shape[1], n_features))
# define model
model = Sequential()
model.add(LSTM(100, activation='relu', return_sequences=True, input_shape=(n_steps_in, n_features)))
model.add(LSTM(100, activation='relu'))
model.add(Dense(n_steps_out))  # output layer is the size of the time steps out
model.compile(optimizer='adam', loss='mse')
# fit model
model.fit(X, y, epochs=50, verbose=0)
Out[38]:
<keras.callbacks.History at 0x7f20c447a510>
In [39]:
# define input sequence
raw_seq = np.array(test_norm[["WS1HA"]])  # Test para Avg Wind Speed

yhat = []
for i in range(len(raw_seq)//n_steps_out):
    x_input = raw_seq[i*n_steps_out:n_steps_in+i*n_steps_out]
    if len(x_input) < n_steps_in: break
    x_input = x_input.reshape((1, n_steps_in, n_features))
    yhat += [model.predict(x_input, verbose=0).flatten()]
yhat = np.array(yhat).reshape(-1,1)

# inverse transform output of the model
yhat = scaler_y.inverse_transform(yhat)
y_test = scaler_y.inverse_transform(pd.DataFrame(raw_seq))

len(yhat), len(y_test)
Out[39]:
(292, 300)
In [ ]:
len(y_test[n_steps_in:]) - len(yhat)
Out[ ]:
-4
In [40]:
plt.figure(figsize=(12,4))
plt.plot(y_test[n_steps_in:], label="Test")
plt.plot(yhat, label="Predicted")
plt.legend()

print("R2:", r2_score(y_test[n_steps_in:], yhat[:len(y_test[n_steps_in:]) - len(yhat)]))
print("MAPE:", mean_absolute_percentage_error(y_test[n_steps_in:], yhat[:len(y_test[n_steps_in:]) - len(yhat)]))
print("MAE:", mean_absolute_error(y_test[n_steps_in:], yhat[:len(y_test[n_steps_in:]) - len(yhat)]))
print("MSE:", mean_squared_error(y_test[n_steps_in:], yhat[:len(y_test[n_steps_in:]) - len(yhat)]))
print("RMSE:", np.sqrt(mean_squared_error(y_test[n_steps_in:], yhat[:len(y_test[n_steps_in:]) - len(yhat)])))
R2: 0.6169246376776117
MAPE: 0.7239814158440532
MAE: 0.7913050143720789
MSE: 1.1499964035624792
RMSE: 1.0723788526274094

Predicciones con salida vector¶

Un modelo LSTM con vector output es una variante de la arquitectura LSTM (Long Short-Term Memory) en la que la capa de salida produce un vector de salida en lugar de una sola predicción. Este tipo de modelo se utiliza cuando se necesita realizar múltiples predicciones o se desea generar una secuencia de salida en lugar de un solo valor.

Aquí hay una descripción general de cómo se podría construir un modelo LSTM con vector output:

  1. Capa LSTM: La primera capa del modelo es una capa LSTM que toma una secuencia de entrada como entrada. La capa LSTM tiene un número de unidades definido por el usuario, que determina la complejidad y la capacidad de memoria del modelo.

  2. Capa de salida densa: Después de la capa LSTM, se agrega una capa densa que produce el vector de salida. Esta capa puede tener una activación específica dependiendo del tipo de salida que se desee. Por ejemplo, si se está realizando una tarea de clasificación, se puede utilizar una activación de softmax. Si se está realizando una tarea de regresión, se puede utilizar una activación lineal o sigmoide.

  3. Compilación del modelo: Después de definir las capas del modelo, se compila utilizando una función de pérdida adecuada para la tarea específica, como MSE (Mean Squared Error) para la regresión o entropía cruzada para la clasificación. También se selecciona un optimizador y métricas de evaluación para supervisar el rendimiento del modelo durante el entrenamiento.

  4. Entrenamiento del modelo: Una vez compilado, el modelo se entrena utilizando un conjunto de datos de entrenamiento. Durante el entrenamiento, los pesos de las capas LSTM y densa se ajustan iterativamente para minimizar la función de pérdida en los datos de entrenamiento.

  5. Predicción: Una vez que el modelo está entrenado, se puede utilizar para hacer predicciones en nuevos datos de prueba. La salida del modelo será un vector que representa las predicciones para cada paso de tiempo o cada elemento de la secuencia de entrada.

Un modelo LSTM con vector output es una arquitectura de red neuronal que utiliza una capa LSTM para procesar secuencias de entrada y produce un vector de salida que puede contener múltiples predicciones o representar una secuencia de salida completa. Este tipo de modelo es útil para una variedad de aplicaciones, como la predicción de series temporales, la generación de texto y la traducción automática.

In [41]:
# multivariate multi-step stacked lstm example
from numpy import array
from numpy import hstack
from keras.models import Sequential
from keras.layers import LSTM
from keras.layers import Dense

# split a multivariate sequence into samples
def split_sequences(sequences, n_steps_in, n_steps_out):
    X, y = list(), list()
    for i in range(len(sequences)):
        # find the end of this pattern
        end_ix = i + n_steps_in
        out_end_ix = end_ix + n_steps_out-1
        # check if we are beyond the dataset
        if out_end_ix > len(sequences):
            break
        # gather input and output parts of the pattern
        seq_x, seq_y = sequences[i:end_ix, :-1], sequences[end_ix-1:out_end_ix, -1]
        X.append(seq_x)
        y.append(seq_y)
    return array(X), array(y)

# define input sequence
# horizontally stack columns
dataset = np.hstack([train_norm[[col]] for col in train_norm.columns])

# choose a number of time steps
n_steps_in, n_steps_out = 8, 4
# covert into input/output
X, y = split_sequences(dataset, n_steps_in, n_steps_out)
# the dataset knows the number of features, e.g. 2
n_features = X.shape[2]
# define model
model = Sequential()
model.add(LSTM(100, activation='relu', return_sequences=True, input_shape=(n_steps_in, n_features)))
model.add(LSTM(100, activation='relu'))
model.add(Dense(n_steps_out))
model.compile(optimizer='adam', loss='mse')
# fit model
model.fit(X, y, epochs=50, verbose=0)
Out[41]:
<keras.callbacks.History at 0x7f20c5e2cd90>
In [42]:
# define input sequence
# horizontally stack columns
dataset = np.hstack([test_norm[[col]] for col in test_norm.columns])

# covert into input/output
X_test, y_test = split_sequences(dataset, n_steps_in, n_steps_out)
In [43]:
yhat = []
for i in range(len(test_norm)//n_steps_out):
    x_input = np.array(test_norm.iloc[i*n_steps_out:n_steps_in+i*n_steps_out, :-1])
    if len(x_input) < n_steps_in: break
    x_input = x_input.reshape((1, n_steps_in, n_features))
    yhat += [model.predict(x_input, verbose=0).flatten()]
yhat = np.array(yhat).reshape(-1,1)
len(yhat)
Out[43]:
296
In [44]:
# inverse transform output of the model
yhat = scaler_y.inverse_transform(yhat)
y_test = scaler_y.inverse_transform(np.array(test_norm[["WS1HA"]]))
In [45]:
plt.figure(figsize=(12,4))
plt.plot(y_test[n_steps_in:], label="Test")
plt.plot(yhat, label="Predicted")
plt.legend()

print("R2:", r2_score(y_test[n_steps_in:], yhat[:len(y_test[n_steps_in:]) - len(yhat)]))
print("MAPE:", mean_absolute_percentage_error(y_test[n_steps_in:], yhat[:len(y_test[n_steps_in:]) - len(yhat)]))
print("MAE:", mean_absolute_error(y_test[n_steps_in:], yhat[:len(y_test[n_steps_in:]) - len(yhat)]))
print("MSE:", mean_squared_error(y_test[n_steps_in:], yhat[:len(y_test[n_steps_in:]) - len(yhat)]))
print("RMSE:", np.sqrt(mean_squared_error(y_test[n_steps_in:], yhat[:len(y_test[n_steps_in:]) - len(yhat)])))
R2: 0.594244308615564
MAPE: 0.6329775673332568
MAE: 0.7587629377433698
MSE: 1.2230446575389806
RMSE: 1.1059134946002696

Red neuronal convolucional¶

Una red neuronal convolucional (CNN por sus siglas en inglés, Convolutional Neural Network) es un tipo de red neuronal profunda especialmente diseñada para el procesamiento de datos que tienen una estructura de grid, como imágenes o datos de series temporales. Las CNN son muy eficaces en tareas relacionadas con la visión por computadora, como la clasificación de imágenes, la detección de objetos, la segmentación semántica y el reconocimiento de patrones.

Las características principales de una CNN son:

  1. Capas convolucionales: Las capas convolucionales aplican filtros a las regiones locales de la entrada para detectar características relevantes, como bordes, texturas o patrones específicos en una imagen. Estos filtros se deslizan sobre la imagen y se aplican de manera repetida para producir mapas de características.

  2. Capas de agrupación (pooling): Las capas de agrupación reducen la dimensionalidad de los mapas de características al resumir la información en regiones locales. El agrupamiento típicamente se realiza mediante operaciones como el máximo o el promedio de un conjunto de valores.

  3. Capas completamente conectadas: Después de aplicar varias capas convolucionales y de agrupación, la información se pasa a una o más capas completamente conectadas que realizan la clasificación o la regresión final.

  4. Funciones de activación: Las funciones de activación, como ReLU (Rectified Linear Unit), se utilizan comúnmente en las capas convolucionales para introducir no linealidades en el modelo y permitir la representación de relaciones más complejas entre las características de entrada y salida.

Las CNN han demostrado ser altamente efectivas en una amplia gama de aplicaciones de visión por computadora debido a su capacidad para aprender automáticamente características relevantes de los datos de entrada, lo que las convierte en una herramienta poderosa para el análisis y procesamiento de imágenes en diversas industrias y campos de investigación.

drawing

CNN como feature extractor¶

Una CNN (Convolutional Neural Network) puede considerarse como un extractor de características (feature extractor) eficaz en tareas de visión por computadora. La arquitectura de una CNN está diseñada específicamente para extraer y aprender automáticamente características relevantes de las imágenes de entrada. Estas características pueden ser patrones visuales como bordes, texturas, formas, o características más abstractas que son útiles para la tarea de clasificación, detección de objetos, segmentación semántica, entre otras.

Las capas convolucionales en una CNN aplican filtros a la imagen de entrada para detectar características relevantes a diferentes niveles de abstracción. Estos filtros se aprenden durante el entrenamiento de la red neuronal y se especializan en detectar patrones específicos en las imágenes. A medida que se profundiza en la red, las capas convolucionales suelen capturar características cada vez más complejas y abstractas.

Además, las capas de agrupación (pooling) ayudan a reducir la dimensionalidad de las características extraídas, conservando la información más relevante y facilitando el proceso de clasificación.

Una CNN actúa como un extractor de características al transformar las imágenes de entrada en representaciones más abstractas y significativas, lo que facilita la tarea de clasificación u otras tareas de visión por computadora.

drawing

Capa convolucional¶

Una capa convolucional es la unidad fundamental de procesamiento en una red neuronal convolucional (CNN). Aquí hay un desglose detallado de cómo funciona una capa convolucional:

  1. Filtros (kernels): En una capa convolucional, se aplican múltiples filtros a la entrada. Cada filtro es una pequeña matriz de pesos que se desliza sobre la entrada y realiza operaciones de convolución para extraer características específicas. Estos filtros son aprendidos durante el entrenamiento de la red y capturan diferentes patrones en los datos, como bordes, texturas o formas.

  2. Operación de convolución: La operación de convolución consiste en deslizar el filtro sobre la entrada y realizar productos punto a punto entre los elementos del filtro y la región correspondiente de la entrada. Esto genera un mapa de características que resalta la presencia de la característica detectada por el filtro en diferentes partes de la entrada. El tamaño del mapa de características resultante depende del tamaño del filtro y de la estrategia de relleno utilizada.

  3. Función de activación: Después de la operación de convolución, se aplica una función de activación no lineal, como ReLU (Rectified Linear Unit), a cada elemento del mapa de características resultante. Esto introduce no linealidades en el modelo y permite la representación de relaciones más complejas entre las características de entrada y salida.

  4. Parámetros entrenables: Los parámetros entrenables en una capa convolucional incluyen los pesos del filtro y los sesgos asociados. Durante el entrenamiento de la red, estos parámetros se ajustan utilizando algoritmos de optimización, como el descenso de gradiente estocástico, para minimizar una función de pérdida definida.

Una capa convolucional en una CNN aplica filtros a la entrada, realiza operaciones de convolución para extraer características, aplica funciones de activación no lineales y opcionalmente realiza operaciones de agrupamiento para reducir la dimensionalidad de los mapas de características resultantes. Estas capas son fundamentales para la capacidad de las CNN de aprender automáticamente características relevantes de los datos de entrada.

drawing

Pooling¶

La capa de agrupación (pooling) es una operación comúnmente utilizada en las redes neuronales convolucionales (CNN) para reducir la dimensionalidad de los mapas de características y, al mismo tiempo, preservar la información más relevante. Aquí tienes una explicación detallada de cómo funciona la capa de agrupación:

  1. Reducción de la dimensionalidad: La capa de agrupación opera sobre los mapas de características generados por las capas convolucionales y reduce su tamaño espacial, es decir, su anchura y altura. Esto se logra al tomar valores (como el máximo o el promedio) en regiones locales de los mapas de características.

  2. Regiones de agrupación: La capa de agrupación divide cada mapa de características en regiones solapadas o no solapadas, dependiendo de los parámetros especificados. Para cada región, se realiza una operación de agrupación para obtener un solo valor representativo.

  3. Operaciones de agrupación: Hay diferentes tipos de operaciones de agrupación comúnmente utilizadas, siendo las más populares el agrupamiento máximo (max pooling) y el agrupamiento promedio (average pooling). En el agrupamiento máximo, se selecciona el valor máximo de cada región de agrupación, mientras que en el agrupamiento promedio, se calcula el valor promedio de los elementos de la región.

  4. Parámetros de la capa de agrupación: Los principales parámetros que se pueden especificar en una capa de agrupación son el tamaño de la región de agrupación (generalmente una ventana cuadrada o rectangular), el paso (stride) que determina la cantidad de desplazamiento de la ventana entre regiones adyacentes, y la estrategia de relleno (padding) para manejar los bordes de la entrada.

  5. Propósito: El propósito principal de la capa de agrupación es reducir la cantidad de parámetros y cálculos en la red neuronal, lo que ayuda a controlar el sobreajuste y a mejorar la eficiencia computacional. Además, la agrupación puede proporcionar cierta invarianza a pequeñas traslaciones y deformaciones en los datos de entrada, lo que hace que el modelo sea más robusto a las variaciones en la posición de las características.

La capa de agrupación en una CNN es una operación clave para reducir la dimensionalidad de los mapas de características, preservando al mismo tiempo la información más relevante, y ayudando así a mejorar el rendimiento y la eficiencia de la red neuronal convolucional.

drawing

CNN para clasificación de imágenes¶

Dataset

CIFAR-10 es un conjunto de datos de referencia ampliamente utilizado en el campo del aprendizaje automático y la visión por computadora. Consiste en un conjunto de 60,000 imágenes a color de 32x32 píxeles, cada una etiquetada con una de las 10 clases de objetos diferentes. Las clases son:

  1. Avión
  2. Automóvil
  3. Pájaro
  4. Gato
  5. Ciervo
  6. Perro
  7. Rana
  8. Caballo
  9. Barco
  10. Camión

El conjunto de datos está dividido en 50,000 imágenes de entrenamiento y 10,000 imágenes de prueba, lo que lo convierte en un estándar para probar algoritmos de reconocimiento de imágenes y clasificación en tareas de visión por computadora.

CIFAR-10 es un conjunto de datos desafiante debido a la baja resolución de las imágenes y a la presencia de objetos en diferentes posiciones, tamaños y condiciones de iluminación. Es utilizado por investigadores y practicantes para desarrollar y evaluar algoritmos de aprendizaje profundo, especialmente en el campo de las redes neuronales convolucionales (CNN), donde ha servido como punto de referencia para evaluar el rendimiento de nuevos modelos y técnicas.

In [46]:
import tensorflow as tf

from tensorflow.keras import datasets, layers, models
import matplotlib.pyplot as plt
import numpy as np
In [47]:
(train_images, train_labels), (test_images, test_labels) = datasets.cifar10.load_data()

# Normalize pixel values to be between 0 and 1
train_images, test_images = train_images / 255.0, test_images / 255.0
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170498071/170498071 [==============================] - 155s 1us/step
In [48]:
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
               'dog', 'frog', 'horse', 'ship', 'truck']

plt.figure(figsize=(10,10))
for i in range(25):
    plt.subplot(5,5,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.imshow(train_images[i])
    # The CIFAR labels happen to be arrays,
    # which is why you need the extra index
    plt.xlabel(class_names[train_labels[i][0]])
plt.show()

A continuación presentamos una arquitectura de CNN (Convolutional Neural Network) relativamente simple pero efectiva.

Aquí tienes una explicación detallada de cada capa:

  1. Capa de entrada (Conv2D):

    • Esta es la primera capa de la red, responsable de procesar las imágenes de entrada.
    • Utiliza 32 filtros de tamaño 3x3 para convolucionar la entrada.
    • Se aplica la función de activación ReLU después de la convolución para introducir no linealidad en la red.
    • La entrada esperada tiene una forma de (32, 32, 3), lo que indica una imagen de 32x32 píxeles con 3 canales de color (RGB).
  2. Capa de agrupación (MaxPooling2D):

    • Después de la convolución, se aplica una capa de agrupación máxima con un tamaño de ventana de 2x2.
    • La capa de agrupación reduce la dimensionalidad de las características extraídas y proporciona invarianza a pequeñas traslaciones y deformaciones en la imagen.
  3. Capa convolucional adicional (Conv2D):

    • Se añade otra capa convolucional similar a la primera, pero con 64 filtros en lugar de 32.
    • Esto permite que la red extraiga características más complejas y abstractas de las imágenes.
  4. Capa de agrupación adicional (MaxPooling2D):

    • Se aplica otra capa de agrupación máxima después de la segunda capa convolucional.
    • Esta capa de agrupación reduce aún más la dimensionalidad de las características y proporciona invarianza adicional a las traslaciones y deformaciones.
  5. Capa de aplanamiento (Flatten):

    • Después de las capas convolucionales y de agrupación, se utiliza una capa de aplanamiento para convertir el tensor de características en un vector unidimensional.
    • Esto permite que las características extraídas se pasen a capas densas completamente conectadas.
  6. Capas densas (Dense):

    • Dos capas densas completamente conectadas siguen a la capa de aplanamiento.
    • La primera capa densa tiene 64 neuronas y utiliza la función de activación ReLU.
    • La segunda capa densa tiene 10 neuronas, que corresponde al número de clases de salida en el problema.
    • No se aplica una función de activación a la capa densa final, lo que significa que las salidas serán logits sin procesar.

Esta arquitectura de CNN consiste en varias capas convolucionales y de agrupación seguidas de capas densas. Es adecuada para problemas de clasificación de imágenes y puede entrenarse eficazmente en conjuntos de datos como CIFAR-10 o MNIST.

In [54]:
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))

model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10))

model.summary()
Model: "sequential_10"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d_3 (Conv2D)           (None, 30, 30, 32)        896       
                                                                 
 max_pooling2d_2 (MaxPooling  (None, 15, 15, 32)       0         
 2D)                                                             
                                                                 
 conv2d_4 (Conv2D)           (None, 13, 13, 64)        18496     
                                                                 
 max_pooling2d_3 (MaxPooling  (None, 6, 6, 64)         0         
 2D)                                                             
                                                                 
 conv2d_5 (Conv2D)           (None, 4, 4, 64)          36928     
                                                                 
 flatten_3 (Flatten)         (None, 1024)              0         
                                                                 
 dense_11 (Dense)            (None, 64)                65600     
                                                                 
 dense_12 (Dense)            (None, 10)                650       
                                                                 
=================================================================
Total params: 122,570
Trainable params: 122,570
Non-trainable params: 0
_________________________________________________________________
In [55]:
model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

history = model.fit(train_images, train_labels, epochs=10,
                    validation_data=(test_images, test_labels))
Epoch 1/10
1563/1563 [==============================] - 92s 58ms/step - loss: 1.5274 - accuracy: 0.4373 - val_loss: 1.2633 - val_accuracy: 0.5537
Epoch 2/10
1563/1563 [==============================] - 65s 42ms/step - loss: 1.1743 - accuracy: 0.5839 - val_loss: 1.0635 - val_accuracy: 0.6320
Epoch 3/10
1563/1563 [==============================] - 64s 41ms/step - loss: 1.0277 - accuracy: 0.6376 - val_loss: 1.0792 - val_accuracy: 0.6211
Epoch 4/10
1563/1563 [==============================] - 89s 57ms/step - loss: 0.9351 - accuracy: 0.6720 - val_loss: 0.9964 - val_accuracy: 0.6538
Epoch 5/10
1563/1563 [==============================] - 77s 49ms/step - loss: 0.8657 - accuracy: 0.6950 - val_loss: 0.9334 - val_accuracy: 0.6781
Epoch 6/10
1563/1563 [==============================] - 73s 46ms/step - loss: 0.8118 - accuracy: 0.7160 - val_loss: 0.9072 - val_accuracy: 0.6830
Epoch 7/10
1563/1563 [==============================] - 74s 47ms/step - loss: 0.7604 - accuracy: 0.7334 - val_loss: 0.8920 - val_accuracy: 0.6940
Epoch 8/10
1563/1563 [==============================] - 75s 48ms/step - loss: 0.7201 - accuracy: 0.7481 - val_loss: 0.9035 - val_accuracy: 0.6923
Epoch 9/10
1563/1563 [==============================] - 76s 49ms/step - loss: 0.6818 - accuracy: 0.7605 - val_loss: 0.8965 - val_accuracy: 0.6987
Epoch 10/10
1563/1563 [==============================] - 71s 45ms/step - loss: 0.6453 - accuracy: 0.7711 - val_loss: 0.8684 - val_accuracy: 0.7051

Gráfico de pérdida de entrenamiento y prueba¶

Un gráfico de train-test loss (pérdida de entrenamiento y prueba) es una herramienta importante para evaluar el rendimiento de un modelo durante el entrenamiento. Aquí hay algunas claves para interpretar este tipo de gráfico:

  1. Eje x (horizontal): Representa el número de iteraciones o épocas de entrenamiento. Cada punto en el eje x corresponde a una iteración completa a través del conjunto de datos de entrenamiento.

  2. Eje y (vertical): Representa la pérdida (error) del modelo en el conjunto de datos. La pérdida es una medida de cuán incorrectas son las predicciones del modelo en comparación con las etiquetas verdaderas. Generalmente, se utiliza una función de pérdida específica según el tipo de problema (por ejemplo, MSE para regresión, entropía cruzada para clasificación).

  3. Curva de entrenamiento (train loss): Esta curva muestra cómo cambia la pérdida del modelo en el conjunto de datos de entrenamiento a lo largo del entrenamiento. Idealmente, queremos que esta curva disminuya a medida que avanza el entrenamiento, lo que indica que el modelo está aprendiendo y ajustándose a los datos de entrenamiento.

  4. Curva de prueba (test loss): Esta curva muestra cómo cambia la pérdida del modelo en un conjunto de datos de prueba independiente que no se utilizó durante el entrenamiento. El objetivo es que esta curva disminuya inicialmente y luego se estabilice o aumente ligeramente a medida que el modelo aprende. Si la pérdida de prueba comienza a aumentar, puede ser un signo de sobreajuste.

  5. Sobreajuste (overfitting): Si la curva de entrenamiento muestra una disminución constante pero la curva de prueba comienza a aumentar, puede indicar que el modelo está sobreajustando los datos de entrenamiento y no generaliza bien a datos nuevos. Esto sugiere que se necesita regularización u otras técnicas para evitar el sobreajuste.

Un gráfico de train-test loss proporciona una visualización útil del rendimiento del modelo durante el entrenamiento y la evaluación. Se utiliza para monitorear el progreso del entrenamiento, detectar signos de sobreajuste y ajustar los hiperparámetros del modelo para obtener un mejor rendimiento general.

In [56]:
plt.plot(history.history['accuracy'], label='accuracy')
plt.plot(history.history['val_accuracy'], label = 'val_accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
#plt.ylim([0.5, 1])
plt.legend(loc='lower right')

test_loss, test_acc = model.evaluate(test_images,  test_labels, verbose=2)
test_acc
313/313 - 4s - loss: 0.8684 - accuracy: 0.7051 - 4s/epoch - 12ms/step
Out[56]:
0.7050999999046326

Un gráfico de train-test usando accuracy muestra cómo la precisión (accuracy) de un modelo varía en el conjunto de entrenamiento y en el conjunto de prueba a lo largo del tiempo o número de épocas durante el entrenamiento del modelo. Este tipo de gráfico es útil para evaluar el rendimiento del modelo tanto en los datos de entrenamiento como en los datos de prueba y para identificar posibles problemas de sobreajuste (overfitting) o subajuste (underfitting).

En el eje x del gráfico se representa el número de épocas o algún otro indicador del progreso del entrenamiento, mientras que en el eje y se representa la precisión del modelo. Por lo general, hay dos líneas en el gráfico: una para la precisión del conjunto de entrenamiento y otra para la precisión del conjunto de prueba. Estas líneas muestran cómo la precisión del modelo evoluciona a medida que se entrena el modelo en diferentes épocas.

Idealmente, esperamos ver que tanto la precisión del conjunto de entrenamiento como la precisión del conjunto de prueba aumenten con el tiempo y eventualmente se estabilicen en un valor constante. Sin embargo, si la precisión del conjunto de entrenamiento sigue aumentando mientras que la precisión del conjunto de prueba comienza a disminuir, esto puede indicar que el modelo está sobreajustando los datos de entrenamiento y no generaliza bien a nuevos datos. Por otro lado, si tanto la precisión del conjunto de entrenamiento como la precisión del conjunto de prueba son bajas y/o estables, esto puede indicar que el modelo está subajustando los datos y no es lo suficientemente complejo para capturar los patrones en los datos.

YOLO¶

YOLO, que significa "You Only Look Once" (Solo miras una vez), es un popular algoritmo de detección de objetos en imágenes desarrollado por Joseph Redmon y colaboradores. Es conocido por su capacidad para detectar objetos en tiempo real con alta precisión y eficiencia.

La principal característica de YOLO es su enfoque único de detección de objetos. A diferencia de otros métodos que dividen la imagen en regiones y luego aplican clasificadores a cada región, YOLO realiza la detección y la clasificación de objetos en una sola pasada de la red neuronal convolucional. Esto significa que YOLO considera todo el contexto de la imagen al hacer predicciones, lo que permite una detección más rápida y precisa.

Algunas de las características clave de YOLO son:

  1. Eficiencia: YOLO es capaz de ejecutar la detección de objetos en tiempo real en dispositivos con recursos limitados, como cámaras de vigilancia o vehículos autónomos.

  2. Precisión: YOLO proporciona una alta precisión en la detección de objetos, incluso en condiciones desafiantes como objetos pequeños, superposición de objetos o baja resolución de imagen.

  3. Unificación: YOLO unifica la tarea de detección y clasificación de objetos en una sola red neuronal, lo que simplifica el proceso y mejora el rendimiento general.

  4. Versatilidad: YOLO es capaz de detectar una amplia variedad de objetos en diferentes categorías, incluyendo personas, vehículos, animales, objetos domésticos, entre otros.

El desarrollo de YOLO ha llevado a numerosas variantes y mejoras, como YOLOv2, YOLOv3, YOLOv4, que han mejorado aún más la precisión y la eficiencia del algoritmo. Estas variantes continúan siendo ampliamente utilizadas en una variedad de aplicaciones, como sistemas de seguridad, vehículos autónomos, sistemas de vigilancia, reconocimiento de objetos en imágenes médicas y más.

YOLO utiliza una CNN (Convolutional Neural Network) como su componente principal. La CNN se utiliza para realizar la detección y clasificación de objetos en una sola pasada de la red neuronal, lo que permite una detección rápida y precisa en tiempo real.

La arquitectura de YOLO utiliza capas convolucionales para extraer características relevantes de la imagen de entrada, seguidas de capas completamente conectadas para realizar la clasificación y localización de objetos. Además, YOLO utiliza una rejilla (grid) sobre la imagen de entrada para predecir las ubicaciones y clases de los objetos.

En resumen, YOLO utiliza una CNN como componente principal para realizar la detección de objetos en imágenes, combinando eficiencia y precisión para aplicaciones en tiempo real.

Ejemplo¶

  • Vamos a usar YOLO para detección y seguimiento de objetos (tracking).

    • Vehículos en una intersección.
  • Vea el resultado obtenido en el siguiente enlace.

Watch the video

  • Dataset resultante:
    • frame: frame del video.
    • id: identificador único del objeto.
    • Coordenada del objeto (x, y).
    • vclass: tipo de vehículo (car, truck, bus).
    • second: transforma el frame en segundos, se divide por el frame rate del video (10 frames/second).
    • my: negativo de y (ajusta el origen (0,0)).
    • velocity: si tenemos distancia recorrida (diferencia de coordenadas) en un tiempo dado, podemos calcular la velocidad.
frame id x y vclass second my velocity
1 1 122.242 402.421 car 0.1 -402.421 0
2 1 122.242 402.421 car 0.2 -402.421 0
3 1 119.318 403.5 car 0.3 -403.5 31.1689
4 1 114.227 401.61 car 0.4 -401.61 54.3068
5 1 106.15 398.43 car 0.5 -398.43 86.7961

Ejemplo de detección de objetos usando la webcam¶

El código en la celda a continuación detecta una de las siguientes clases usando la cámara web de su computador.

classNames = ["person", "bicycle", ...
              ]
In [ ]:
from ultralytics import YOLO
import cv2
import math
# start webcam
cap = cv2.VideoCapture(0)
cap.set(3, 640)
cap.set(4, 480)

# model
model = YOLO("yolo-Weights/yolov8n.pt")

# object classes
classNames = ["person", "bicycle", "car", "motorbike", "aeroplane", "bus", "train", "truck", "boat",
              "traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat",
              "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella",
              "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat",
              "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup",
              "fork", "knife", "spoon", "bowl", "banana", "apple", "sandwich", "orange", "broccoli",
              "carrot", "hot dog", "pizza", "donut", "cake", "chair", "sofa", "pottedplant", "bed",
              "diningtable", "toilet", "tvmonitor", "laptop", "mouse", "remote", "keyboard", "cell phone",
              "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors",
              "teddy bear", "hair drier", "toothbrush"
              ]


while True:
    success, img = cap.read()
    results = model(img, stream=True)

    # coordinates
    for r in results:
        boxes = r.boxes

        for box in boxes:
            # bounding box
            x1, y1, x2, y2 = box.xyxy[0]
            x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2) # convert to int values

            # put box in cam
            cv2.rectangle(img, (x1, y1), (x2, y2), (255, 0, 255), 3)

            # confidence
            confidence = math.ceil((box.conf[0]*100))/100
            print("Confidence --->",confidence)

            # class name
            cls = int(box.cls[0])
            print("Class name -->", classNames[cls])

            # object details
            org = [x1, y1]
            font = cv2.FONT_HERSHEY_SIMPLEX
            fontScale = 1
            color = (255, 0, 0)
            thickness = 2

            cv2.putText(img, classNames[cls], org, font, fontScale, color, thickness)

    cv2.imshow('Webcam', img)
    if cv2.waitKey(1) == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

YOLO tracking¶

El código siguiente realiza detección y trackin de 3 tipos de vehículos: car, bus, truck.

Asigna un indicador único a cada vehículo, una coordenada x, y.

Podemos almacenar esta información en un dataset para poder visualizarla después.

In [ ]:
import cv2
import numpy as np
from ultralytics import YOLO

from ultralytics.utils.checks import check_imshow
from ultralytics.utils.plotting import Annotator, colors

from collections import defaultdict

# object classes
classNames = ["person", "bicycle", "car", "motorbike", "aeroplane", "bus", "train", "truck", "boat",
              "traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat",
              "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella",
              "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat",
              "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup",
              "fork", "knife", "spoon", "bowl", "banana", "apple", "sandwich", "orange", "broccoli",
              "carrot", "hot dog", "pizza", "donut", "cake", "chair", "sofa", "pottedplant", "bed",
              "diningtable", "toilet", "tvmonitor", "laptop", "mouse", "remote", "keyboard", "cell phone",
              "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors",
              "teddy bear", "hair drier", "toothbrush"
              ]

# Load the YOLOv8 model
model = YOLO('yolov8n.pt')

# Open the video file
video_path = "Triomphe.mp4"
cap = cv2.VideoCapture(video_path)

# Store the track history
track_history = defaultdict(lambda: [])
# Store the class history
cls_history = defaultdict(lambda: [])

frame_number = 0

# Loop through the video frames
while cap.isOpened():
    # Read a frame from the video
    success, frame = cap.read()

    if success:
        frame_number += 1
        # Run YOLOv8 tracking on the frame, persisting tracks between frames
        # detect only classes=[2,5,7], car, bus, truck
        results = model.track(frame, persist=True, classes=[2,5,7])

        # Get the boxes and track IDs
        boxes = results[0].boxes.xywh.cpu()
        clss = results[0].boxes.cls
        track_ids = results[0].boxes.id.int().cpu().tolist()

        # Visualize the results on the frame
        annotated_frame = results[0].plot()

        # Plot the tracks
        for box, track_id, cls in zip(boxes, track_ids, clss):
            x, y, w, h = box
            track = track_history[track_id]
            ch = cls_history[track_id]
            track.append((float(x), float(y)))  # x, y center point
            ch.append((classNames[int(cls)], frame_number))
            # class name
            # print("Class name -->", classNames[int(cls)])

            # Draw the tracking lines
            points = np.hstack(track).astype(np.int32).reshape((-1, 1, 2))
            cv2.polylines(annotated_frame, [points], isClosed=False, color=(255, 255, 0), thickness=2)

        # Display the annotated frame
        cv2.imshow("YOLOv8 Tracking", annotated_frame)

        # Break the loop if 'q' is pressed
        if cv2.waitKey(1) & 0xFF == ord("q"):
            break
    else:
        # Break the loop if the end of the video is reached
        break

# Release the video capture object and close the display window
cap.release()
cv2.destroyAllWindows()

Obtención de la información de los objetos y creación de dataset¶

In [58]:
x = []
y = []
object_id = []
object_class = []
frame_id = []

for k in track_history.keys():
    points = track_history[k]
    x += list(np.array(points)[:, 0])
    y += list(np.array(points)[:, 1])
    object_id += len(track_history[k])*[k]
    extra = cls_history[k]
    object_class += list(np.array(extra)[:, 0])
    frame_id += list(np.array(extra)[:, 1])
In [59]:
len(x), len(y), len(frame_id), len(object_id), len(object_class)
Out[59]:
(40871, 40871, 40871, 40871, 40871)
In [61]:
import pandas as pd
data = pd.DataFrame({'frame': np.array(frame_id).astype('int'), 'id': object_id, 'x': x, 'y': y, 'class': object_class})
data.head()
Out[61]:
frame id x y class
0 1 1 122.241806 402.421326 car
1 2 1 122.241806 402.421326 car
2 3 1 119.317543 403.500061 car
3 4 1 114.226517 401.609619 car
4 5 1 106.150299 398.429932 car
In [64]:
plt.bar(*np.unique(data['class'], return_counts=True))
Out[64]:
<BarContainer object of 3 artists>
In [65]:
# dividimos entre el framerate para tener segundos
data['second'] = data['frame']/10
# negativo de la coordenada y para corregir origen (0, 0)
data['my'] = data['y']*-1
In [66]:
import seaborn as sns
import matplotlib.pyplot as plt
In [67]:
plt.figure(figsize=(16,8))

sns.kdeplot(
    data=data, x="x", y="my", hue='class',
    fill=True, common_norm=False, alpha=0.5
)
#a=plt.axis('off')
plt.xlabel('')
plt.ylabel('')
xt=plt.xticks([])
yt=plt.yticks([])
plt.show()
In [73]:
from scipy.spatial import distance

# Calculamos la velocidad
data['velocity'] = 0
for car in np.unique(data['id']):
    dp = data.query('id == '+str(car))
    dt = [t for t in zip(dp['x'], dp['y'], dp['second'])]
    v = [distance.euclidean(dt[i-1][:2], dt[i][:2])/(abs(dt[i-1][2]-dt[i][2])) for i in range(1,len(dt))]
    data.loc[dp.index[1:], 'velocity'] = v
    
data.head()
Out[73]:
frame id x y vclass second my velocity
0 1 1 122.241806 402.421326 car 0.1 -402.421326 0.000000
1 2 1 122.241806 402.421326 car 0.2 -402.421326 0.000000
2 3 1 119.317543 403.500061 car 0.3 -403.500061 31.168869
3 4 1 114.226517 401.609619 car 0.4 -401.609619 54.306831
4 5 1 106.150299 398.429932 car 0.5 -398.429932 86.796143

Visualización de los resultados¶

In [74]:
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

data = pd.read_csv('https://raw.githubusercontent.com/marsgr6/ml-online/main/data/Triomphe.csv')
data.head()
Out[74]:
frame id x y vclass second my velocity
0 1 1 122.241806 402.421326 car 0.1 -402.421326 0.000000
1 2 1 122.241806 402.421326 car 0.2 -402.421326 0.000000
2 3 1 119.317543 403.500061 car 0.3 -403.500061 31.168869
3 4 1 114.226517 401.609619 car 0.4 -401.609619 54.306831
4 5 1 106.150299 398.429932 car 0.5 -398.429932 86.796143
In [75]:
sns.set_context('poster')
plt.figure(figsize=(12,6))
plt.plot(data.groupby('second')['x'].count(), label='All vehicles');
plt.plot(data.query('vclass == "car"').groupby('second')['x'].count(), label='Car');
plt.plot(data.query('vclass == "bus"').groupby('second')['x'].count(), label='Bus');
plt.plot(data.query('vclass == "truck"').groupby('second')['x'].count(), label='Truck');
plt.xlabel("Time (seconds)")
plt.ylabel("Vehicles count")
plt.legend()
plt.show()
In [79]:
sns.set_context('poster')
plt.figure(figsize=(16,12))
sns.scatterplot(data=data, x='frame', y='id', hue='vclass', s=5, alpha=0.7)
plt.legend(markerscale=1)
#plt.axis('off');
Out[79]:
<matplotlib.legend.Legend at 0x7f1f097aadd0>
In [80]:
sns.set_context('poster')
plt.figure(figsize=(16,12))
sns.scatterplot(data=data, x='x', y='my', hue='vclass', s=5, alpha=0.7)
plt.axis('off');

Gráfico quiver¶

Un gráfico quiver, también conocido como gráfico de flechas o diagrama de vectores, es una representación visual que muestra la dirección y magnitud de un campo vectorial en un espacio bidimensional o tridimensional. Este tipo de gráfico es comúnmente utilizado en física, ingeniería, ciencias de la atmósfera y otros campos donde es necesario visualizar vectores en un plano o en el espacio.

En un gráfico quiver, cada flecha representa un vector y su longitud y dirección representan la magnitud y dirección del vector, respectivamente. La flecha se coloca en una posición específica del plano o espacio que corresponde al punto donde se encuentra el vector.

Los gráficos quiver son útiles para visualizar campos vectoriales, como el campo de velocidades en un flujo de fluidos, el campo eléctrico en un campo eléctrico o el campo de viento en meteorología. Además de mostrar la dirección y magnitud de los vectores, los gráficos quiver pueden ayudar a identificar patrones, flujos, áreas de convergencia o divergencia, y otras características del campo vectorial.

En Python, los gráficos quiver se pueden crear utilizando bibliotecas de visualización como Matplotlib. Estas bibliotecas proporcionan funciones especializadas para trazar vectores y personalizar la apariencia del gráfico quiver según las necesidades del usuario.

In [81]:
from scipy.ndimage import uniform_filter1d

data['u'] = uniform_filter1d(data.x, size=1)
data['v'] = uniform_filter1d(data.my, size=1)

plt.figure(figsize=(16,12))
plt.quiver(data.x, data.my, data.u, data.v)
plt.axis('off');

Gráfico de densidad múltiple (KDE plot)¶

  1. sns.kdeplot: Esta es la función de Seaborn utilizada para trazar un gráfico de densidad de kernel.

  2. data=data: Esto especifica el DataFrame data como el origen de los datos para el gráfico.

  3. x="velocity": Esto especifica que la variable "velocity" del DataFrame data se utilizará en el eje x del gráfico.

  4. hue="vclass": Esto especifica que la variable "vclass" del DataFrame data se utilizará para agrupar los datos y se representará usando diferentes colores en el gráfico.

  5. multiple="fill": Esto especifica que se debe trazar múltiples curvas de densidad de kernel, una para cada categoría de "vclass", y se rellenan las áreas debajo de las curvas para facilitar la comparación.

  6. common_norm=False: Esto especifica que cada curva de densidad de kernel se normalizará por separado, lo que significa que la suma de las áreas bajo cada curva será igual a 1.

En resumen, este código crea un gráfico de densidad de kernel donde se muestra la distribución de la variable "velocity" del DataFrame data, con las diferentes categorías de la variable "vclass" representadas por diferentes colores y áreas rellenadas debajo de las curvas de densidad.

In [82]:
plt.figure(figsize=(8,6))
ax=sns.kdeplot(data=data, x="velocity", hue="vclass", multiple="fill", common_norm=False)
sns.move_legend(ax, "upper left", bbox_to_anchor=(1, 1))

Diagrama de Voronoi¶

Un diagrama de Voronoi, también conocido como teselación de Voronoi o partición de Voronoi, es una herramienta matemática y geométrica que divide un espacio en regiones basadas en la proximidad a un conjunto específico de puntos llamados "sitios" o "generadores". Cada región en el diagrama de Voronoi está asociada con un sitio y contiene todos los puntos en el espacio que están más cerca de ese sitio que de cualquier otro.

Para construir un diagrama de Voronoi, se comienza con un conjunto de puntos llamados "sitios" o "generadores". Luego, se traza una línea entre cada par de sitios, creando así límites entre las regiones asociadas con cada sitio. Estas líneas dividen el espacio en polígonos, donde cada polígono representa la región de influencia de un sitio.

Los diagramas de Voronoi tienen muchas aplicaciones en diversos campos, como la geometría computacional, la cartografía, la planificación urbana, la ciencia de datos, la robótica y la visualización de datos. Algunas de las aplicaciones comunes incluyen la optimización de rutas, la interpolación de datos espaciales, la segmentación de imágenes y la generación de mapas de proximidad.

Un diagrama de Voronoi es una partición de un espacio en regiones basadas en la proximidad a un conjunto de puntos específicos, lo que lo convierte en una herramienta útil para analizar y comprender la distribución espacial de datos.

In [83]:
from scipy.spatial import Voronoi, voronoi_plot_2d
from scipy.spatial import distance
from scipy.interpolate import griddata
from scipy import ndimage

points = np.array(data[['x', 'my']])

vor = Voronoi(points)

fig, ax = plt.subplots(figsize=(16,8))

figv = voronoi_plot_2d(vor, show_vertices=False, line_colors='orange',
                line_width=2, line_alpha=0.6, point_size=2, ax=ax)

plt.xlabel('')
plt.ylabel('')
xt=plt.xticks([])
yt=plt.yticks([])
#a=plt.axis('off')
In [84]:
from scipy.spatial import Voronoi
from shapely.geometry import Polygon

def voronoi_finite_polygons_2d(vor, radius=None):
    """
    Reconstruct infinite voronoi regions in a 2D diagram to finite
    regions.
    Parameters
    ----------
    vor : Voronoi
        Input diagram
    radius : float, optional
        Distance to 'points at infinity'.
    Returns
    -------
    regions : list of tuples
        Indices of vertices in each revised Voronoi regions.
    vertices : list of tuples
        Coordinates for revised Voronoi vertices. Same as coordinates
        of input vertices, with 'points at infinity' appended to the
        end.
    """

    if vor.points.shape[1] != 2:
        raise ValueError("Requires 2D input")

    new_regions = []
    new_vertices = vor.vertices.tolist()

    center = vor.points.mean(axis=0)
    if radius is None:
        radius = vor.points.ptp().max()*2

    # Construct a map containing all ridges for a given point
    all_ridges = {}
    for (p1, p2), (v1, v2) in zip(vor.ridge_points, vor.ridge_vertices):
        all_ridges.setdefault(p1, []).append((p2, v1, v2))
        all_ridges.setdefault(p2, []).append((p1, v1, v2))

    # Reconstruct infinite regions
    for p1, region in enumerate(vor.point_region):
        vertices = vor.regions[region]

        if all(v >= 0 for v in vertices):
            # finite region
            new_regions.append(vertices)
            continue

        # reconstruct a non-finite region
        if p1 in all_ridges:
            ridges = all_ridges[p1]
            new_region = [v for v in vertices if v >= 0]

            for p2, v1, v2 in ridges:
                if v2 < 0:
                    v1, v2 = v2, v1
                if v1 >= 0:
                    # finite ridge: already in the region
                    continue

                # Compute the missing endpoint of an infinite ridge

                t = vor.points[p2] - vor.points[p1] # tangent
                t /= np.linalg.norm(t)
                n = np.array([-t[1], t[0]])  # normal

                midpoint = vor.points[[p1, p2]].mean(axis=0)
                direction = np.sign(np.dot(midpoint - center, n)) * n
                far_point = vor.vertices[v2] + direction * radius

                new_region.append(len(new_vertices))
                new_vertices.append(far_point.tolist())

            # sort region counterclockwise
            vs = np.asarray([new_vertices[v] for v in new_region])
            c = vs.mean(axis=0)
            angles = np.arctan2(vs[:,1] - c[1], vs[:,0] - c[0])
            new_region = np.array(new_region)[np.argsort(angles)]

            # finish
            new_regions.append(new_region.tolist())

    return new_regions, np.asarray(new_vertices)

# make up data points
np.random.seed(1234)
#points = np.random.rand(15, 2)

# compute Voronoi tesselation
vor = Voronoi(points)

plt.figure(figsize=(16,8))

# plot
regions, vertices = voronoi_finite_polygons_2d(vor)

min_x = vor.min_bound[0] - 0.1
max_x = vor.max_bound[0] + 0.1
min_y = vor.min_bound[1] - 0.1
max_y = vor.max_bound[1] + 0.1

mins = np.tile((min_x, min_y), (vertices.shape[0], 1))
bounded_vertices = np.max((vertices, mins), axis=0)
maxs = np.tile((max_x, max_y), (vertices.shape[0], 1))
bounded_vertices = np.min((bounded_vertices, maxs), axis=0)


box = Polygon([[min_x, min_y], [min_x, max_y], [max_x, max_y], [max_x, min_y]])

# colorize
for region in regions:
    polygon = vertices[region]
    # Clipping polygon
    poly = Polygon(polygon)
    poly = poly.intersection(box)
    polygon = [p for p in poly.exterior.coords]

    plt.fill(*zip(*polygon), alpha=0.4)

#plt.plot(points[:, 0], points[:, 1], 'o', color="gray", markersize=1)
plt.axis('equal')
plt.xlim(vor.min_bound[0] - 0.1, vor.max_bound[0] + 0.1)
plt.ylim(vor.min_bound[1] - 0.1, vor.max_bound[1] + 0.1)
a=plt.axis('off')

Gráfico tripcolor¶

Un gráfico tripcolor es un tipo de visualización tridimensional en la que se colorean los triángulos de una malla en función de un valor asociado a cada uno de ellos. Es especialmente útil para representar datos tridimensionales de manera visual y comprensible.

En un gráfico tripcolor, cada triángulo en la malla se representa como un área coloreada en un plano tridimensional. El color de cada área está determinado por un valor numérico que se asocia a ese triángulo en particular. Este valor puede ser cualquier cosa, como una altura, una temperatura, una densidad, etc.

Los gráficos tripcolor son comúnmente utilizados en disciplinas como la visualización científica, la geografía, la oceanografía, la meteorología y otros campos donde es importante representar datos tridimensionales de manera clara y comprensible. Ayudan a identificar patrones, tendencias y relaciones en los datos de una manera intuitiva y efectiva.

In [85]:
plt.figure(figsize=(16,12))
plt.tripcolor(data.x, data.my, data.velocity)
plt.axis('off');
In [88]:
sns.set_context('poster')
plt.figure(figsize=(16,12))
sns.scatterplot(data=data, x='x', y='my', hue='velocity', size="velocity",
                alpha=0.7, legend=False, sizes=(1, 100),
                palette=sns.color_palette("icefire", as_cmap=True))
plt.axis('off');

Fuentes y recursos¶

  • https://www.tensorflow.org/tutorials/images/cnn
  • https://machinelearningmastery.com/how-to-develop-lstm-models-for-time-series-forecasting/
  • https://www.tensorflow.org/tutorials/images/cnn
  • https://www.analyticsvidhya.com/blog/2021/05/convolutional-neural-networks-cnn/
  • https://towardsdatascience.com/covolutional-neural-network-cb0883dd6529